© 2014 by The Document Company Xerox and Xerox Research
Centre Europe. All rights reserved.
Copyright protection claimed includes all forms and
matters of copyrightable material and information now allowed by statutory or
judicial law or hereinafter granted, including without limitation, material
generated from the software programs which are displayed on the screen such as
icons, screen displays, looks, etc.
Printed in France
XIP ®, Xerox ®, The Document Company®, and all Xerox
products mentioned in this publication are trademarks of Xerox Corporation.
Author: Claude Roux
E-mail : claude.roux@xrce.xerox.com
4 Error on key:
erroronkey(bool)
4 Garbage information: map
m=gcsize();
4 Garbage function:
garbagefunction(function);
Passing
arguments to a KiF program
4 Separator in pathnames:
_sep
4 XIP Objects (only
available from within the XIP interpreter):
function, polynomial, autorun,
thread
Specific
flags: private & strict
4 private [function |
thread | autorun | polynomial]
4 strict [function |
thread | polynomial]
4 Creation within the
constructor
Sub-framing
or enriching a frame
4 Using upper definition:
frame::function
Associate
Functions: WITH operator
KiF
is a contextual programming language.
XIP
Regular Expression Formalism
4 The operators *,+, () ,
([] )
4 lisp() or lisp(string opening,
string closing)
4 tags(string opening,
string closing)
Type
bits (sparse representation of bits)
4 As a string, an integer
or a float
4 Extracting variables
from a vector
4 Example (sorting out
integers in a vector)
4 Example (sorting out
integers in a vector but seen as strings)
4 Example: modification
of each element of a vector with a function
Type
ivector, fvector, svector,table
4 Type ivector, fvector,
svector
Type
map (treemap and primemap)
4 mapi,mapf, treemapi,
treemapf, primemapi, primemapf
4 s(tree|prime)map,i(tree|prime)map,f(tree|prime)map
4 is(tree|prime)map,
ii(tree|prime)map and if(tree|prime)map
4 fs(tree|prime)map,
fi(tree|prime)map and ff(tree|prime)map
Logical
Operators on value containers: &,|,^
4 Executing External
Functions
4 Session: open, clean,
compile, run
switch
(expression) (with function) {…}
4 for
(expression;boolean;next) {…}
4 Multiple
initializations and increments
4 for (i in
<start,end,increment>): Fast loop
Evaluation:
eval(string code);
print,
println, printerr,printlnerr
printj,
printjln, printjerr,printjlnerr
4 Example: Simple
Container Function
4 Example: More complex
Container Function
Functional
Language: ŕ la Haskell
Before
starting: some new operators
4 Two new operators:
&&& and ::
4 Declaring a
Haskell-like instruction
4 case x of pattern ->
result, pattern -> result… otherwise result
4 Iteration on list in
the arguments…
4 Calling a function in a
Haskell expression
4 <takeWhile
(condition) list>
4 <dropWhile
(condition) list>
4 <foldl|foldr (f)
first list>
Variables
with functions: Associate Functions
4 Basic types: int,
string, float etc.
4 Containers: vector, map
etc.
Semaphores:
waitonfalse and synchronous.
4 _predicatemode(mode,compute_final,compute_inter)
4 Other inference types: list and associative map
4 Common mistakes with
KiF variables.
4 Ancestor again but with
a database
4 Ancestor (last), with
assertdb instead of store
LMDB
(Lightning Memory-Mapped Database)
4 The input structure to
both predict and trainingset
Fast
Light ToolKit library (GUI)
4 Creating windows in a
thread
4 Callbacks: scrolling,
mouse and keyboard
Type wordnet (Based on WordNet 3.0)
4 int
KifLoad(vector<string>& paths,vector<string>&
arguments,bool debugkif)
4 TemplateName_KifInitialisationModule
4 Specific Objects: VERY
IMPORTANT
4 KifTry: String(),
Integer(), Float(), Boolean()
4 KifTry: less,
different, more, same: <,>,=,!=,<=,>=
4 Newfield(string
name,KifElement* value);
4 Getfield(string
name,KifDomain* domain);
4 Passing arguments to a
KiF program: Specific to XIP
4 Garbage collector
(Specific to XIP)
4 int
XipKifLoad(vector<string>& paths,vector<string>&
arguments,bool debugkif)
Type
fst: Xerox Finite-state transducers
4 Executing External
Functions
This document
describes the KiF language.
The KiF
language is part of the XIP engine. This interpreted language shares the same
variables as the XIP script language. The functions and frames defined in a KiF
section can be fully called and integrated in any XIP rules. Furthermore, a KiF
program can also be executed from the command line with the XIP engine, without
any grammars.
The KiF
language borrows many concepts from many other languages, mainly C++ and
Python. It is therefore quite straightforward to learn for someone with a basic
knowledge of theses languages.
The most
important element of this language is that the interpretation of a variable or
of an operator depends on its context.
A KiF program
contains variable declarations, function declarations and frames (or classes)
declarations. A variable can be declared anywhere at any place, the same
applies to functions, to the exception of loops.
•
KiF is
case-sensitive, which is not the case for XIP
•
The KiF
language is fully compliant with XIP variable declarations.
•
A variable
declared in XIP is visible as such in KiF.
•
Any
modification of a XIP variable in a KiF program shows up in XIP script
language.
Comments for a line are introduced
anywhere with a //.
//This is
a comments
Comments for a bloc of lines are
inserted into: /@...@/
/@
This is…
a bloc of
comments.
@/
A function is declared with the keyword
function, a name and some parameters.
A frame is declared with the keyword frame, followed with a name. A sub-frame
is simply declared as a frame within a frame.
By default,
any attempt to access a value in a map with an unknown key does not raise an exception.
The function: erroronkey(bool),
which should be placed at the very beginning of your code modifies this
behavior.
Functions and frames can be declared in
advance, before their actual implementations.
function toto(int i);
frame test;
KiF provides a function which returns
some information about the garbage collector as a map. This map has three
values:
·
deleted:
this
is the size of the deleted vector in which free cells from the garbage are
kept.
·
cursor: this is the position of the last
element in the deleted vector which has been reached before calling the garbage collector.
·
garbage: this is the actual size of the garbage
collector
·
cursor…: specific garbages are also managed by
KiF for strings, integers, floats, vectors, maps and lists. These structures
are actually pre-created when KiF is initialized. KiF never actually deletes
these elements from memory, but reuses them whenever their reference is back to
0. This explains why the specific cursors might move across the different
garbages back and forth in a random way.
KiF provides a simple mechanism to
detect and check when the garbage collector is called. When garbagefunction
is implemented at the beginning of a KiF program, with as a parameter a
specific function name, then when the garbage collector is called, this
function will be executed.
function displaygc() {
printlnerr("Garbage stats:",gcsize());
}
garbagefunction(displaygc);
A KiF program
is usually called with KiF with a list of arguments. Each of these arguments is
then available to the KiF program through two specific systems variables: _args and _paths.
KiF myprogram c:\test\mytext.txt
Kif provides a
specific variable: “_args”, which actually
is a string vector in which each argument is stored according to its position
in the declaration list.
file f;
f.openread(_args[0]);
KiF
provides a second vector variable: _paths,
which stores the pathnames of the different KiF
programs, which have been loaded.
//Displaying all paths loaded in
memory
iterator it=_paths;
for (it.begin();it.nend();it.next())
print("Loaded: ",it.value(),"\n");
The first
element of this vector: _paths[0] stores
the current directory pathname.
Windows and Unix do not use exactly the
same carriage return, as on Windows, a carriage return is usually two character
long: “\r\n”.
_endl
returns the proper carriage return according to the platform value.
Unix-based systems and Windows use
different separators in pathnames, between directory names. Unix requires a “/”
while Windows requires a “\”.
KiF provides a specific variable: _sep, which returns the right separator in
accordance with the current system.
KiF provides a default console, which can be used to
load and edit any programs. The console can be used to test small pieces of
code or to check the values at the end of an execution.
You can also execute a program in a debug mode, which
then displays the content of the stack and of the variables at each step in
your program.
To launch the console, run KiF with: -console.
N.B. A console is also available with xip, the option
in this case is:
-kifconsole.
KiF requires all
items to be declared with a specific type. Types are either predefined or user-defined
as a frame.
KiF provides
many different types, which for some of them, are directly related to the XIP
rule language. We will come back with more details on these types later on.
Most of them are pretty standard in most programming languages except for the
XIP object.
A function is
declared anywhere in the code, using the keyword function.
A frame is a user defined type which is
very similar to a class in other languages. A frame contains as many variables or function definitions as
necessary.
A variable is declared as in many
language by giving first the type of the variable, then a list of variable
names, separated by commas and ending with a “;”.
//each variable can be
individually instantiated in the list
int i,j,k=10;
string s1="s1",s2="s2";
A variable can
be declared as private, which in many
cases is rather useful. For instance, a frame variable can be declared as
private in order to prevent a client in a server application to have access to
it.
private test toto;
Since an
example is better than a hundred lines of explanation, here is a small program,
which simply displays the content of a string
//declaration
string s;
int i;
//Instantiation
s="abcd";
i=100;
//Display
print("S=",s," I=",i,"\n");
S=abcd I=100
A function is
declared with the keyword function,
followed with its name and parameters. A value can be returned with the keyword
return. Parameters are always sent as
values except if the type is self. It should be noted that a function does
not provide any types for its return value.
A polynomial function is a numerical
function, whose result is automatically indexed on the input parameters. If a
specific set of parameters has already been processed, then the value computed
for these parameters is extracted and returned in lieu of computing it again.
polynomial compute(int
j) {
float k=cos(j)*sin(j)^10;
return(k);
}
The first call to compute(10), will trigger KiF to actually compute the value:
-8.39072 for k.
If compute(10)
is called once again, then the system will use its internal index associated to
10 to return the same value. The formula will not be computed again.
Polynomial
automatically builds a map in memory based on the input parameters. These
functions consume some resources and should be used with caution.
An autorun
function is automatically launched after its declaration.
Note: autorun are useless in frames.
autorun waitonred() {
println(“Loading:”,_paths[1]);
}
When a thread function is launched, it is executed into an independent
thread.
function toto(int
i) {
i+=10;
return(i);
}
int j=toto(10);
print("J="+j+"\n");
J=20
“protected”
prevents two threads to access the same
lines of code at the same time.
A protected
thread sets a lock (see below) at
the beginning of its launch, which is released once the function is executed.
Thus, different calls to a protected function will be done in a sequence and
not at the same time. Protected
should be used for code that is not reentrant.
//We implement a synchronized
thread
protected thread launch(string n,int m) {
int i;
println(n);
//we display all our values
for (i=0;i<m;i++)
print(i,"
");
println();
}
function principal() {
//we launch our thread
launch("Premier",2);
launch("Second",4);
println("End");
}
principal();
End
Premier
0 1
Second
0 1 2
Exclusive is very similar to protected,
with one difference. In the case of protected,
the protection is at the method level, while with exclusive it is at the object level. In this sense, exclusive works exactly as synchronized in Java.
In the case of
a protected function, only one thread
can have access to this method at a
time, while if a method is exclusive,
only one thread can have access to the object
at a time, which means that different threads can execute the same method if this
method is executed within different instances.
In other words,
in a protected thread, we use a lock
that belongs to the method, while in an exclusive
thread, we use a lock that belongs to the frame instance.
exclusive thread
framethod(..) { lock(instanceid)…}
protected thread
method(…) {lock(methodid)…}
//This frame exposes two methods
frame disp {
//exclusive
exclusive thread edisplay(string s) {
println("Exclusive:",s);
}
//protected
protected thread pdisplay(string s) {
println("Protected:",s);
}
}
//We also implement a task frame
frame task {
string
s;
//with a specific "disp" instance
disp
cx;
function
_initial(string x) {
s=x;
}
//Then we propose three methods
//We call our local instance with protected
function
pdisplay() {
cx.pdisplay(s);
}
//We call our local instance with exclusive
function
edisplay() {
cx.edisplay(s);
}
//we call the global instance with exclusive
function
display(disp c) {
c.edisplay(s);
}
}
//the common instance
disp c;
vector v;
int i;
string s="T";
for (i=0;i<100;i++) {
s="T"+i;
task
t(s);
v.push(t);
}
//In this case, the display will
be ordered as protected is not reentrant
//only one pdisplay can run at a
time
for (i=0;i<100;i++)
v[i].pdisplay();
//In this case, the display will
be a mix of all edisplay working in parallel
//since, exclusive only protects
methods within one instance, and we have different
//instances in this case...
for (i=0;i<100;i++)
v[i].edisplay();
//In this last case, we have one
single common object "disp" shared by all "task"
//The display will be again
ordered as with protected, since this time we run into the same
// "c disp" instance.
for (i=0;i<100;i++)
v[i].display(c);
A thread can be declared as joined, if the main thread is supposed to
wait for the completion of all the threads that were launched before completing
its own code, you can use waitonjoined()
which will then wait for these threads to finish.
You can use as many waitonjoined() as necessary in different
threads. waitonjoined only waits on “joined threads” that were launched
within a given thread.
When you
launch a thread, the result of that thread cannot be directly stored in a
variable with the operator “=”, since a thread lives its own life without any
links to the calling code. KIF provides a specific operator for this task:
<<<, which is called the stream operator. A stream is a variable which
is connected to the thread in such a way that the values returned by the thread
can be stored within that variable. The only constraint is the life of this
variable should at least be the one of the thread.
//we create a thread as a "join" thread, in
order to be able to use waitonjoined.
//This thread simply returns 2xi
joined thread Test(int i) {
return(i*2);
}
//Our launch function, which will launch 10 threads
function launch() {
//we first declare within this function our map
storage variable
treemapi m;
int i=0;
//we then launch 10 threads, and we store the result
of each into m at a specific position
for (i in
<0,10,1>)
m[i]<<<Test(i);
//we wait for all threads to finish
waitonjoined();
//we display our final value:
println(m); //{0:0,1:2,2:4,3:6,4:8,5:10,6:12,7:14,8:16,9:18}
}
launch();
KiF allows for
multiple definitions of functions sharing the same name, however differing in
their parameter definition. For instance, one can implement a display(string s) and a display(int s).
In this case, when more than one function is
implemented sharing the same name, the argument control is much stricter than
with one single implementation as the system will try to find, which function
is the most suitable according to the argument list of the function call. Thus,
the mechanism through which arguments are translated into a value suitable for
a function parameter is no longer available.
function testmultipledeclaration(string s,
string v) {
println("String:",s,v);
}
function testmultipledeclaration(int s, int v) {
println("Int:",s,v);
}
//we declare our variables
int i;
int j;
string s1="s1";
string s2="s2";
//In this case, the system will
choose the right function according to its argument list...
testmultipledeclaration(s1,s2); //the string implementation
testmultipledeclaration(i,j); //the integer implementation
To the difference with C++ for
instance, KiF does not consider selection ambiguity as an error. KiF will
therefore select the first function
that matches the argument list of the calling function, which means that the
order in which functions are declared is important.
A function
might be called in a specific context, whose knowledge can be useful in many
cases. This context is available through the environment() function, which returns this context as a string or
as an integer.
//We define our function
function toto(int
i) {
string s=environment();
print("Environment:",s,"\n");
return(i+10);
}
//We use it in an expression
int j=toto(10);
string s=toto(10);
Environment: int
It is possible
to declare a function with an unlimited number of arguments. In this case, the
end of the declaration should be “…”. The arguments are then accessible through
the name of the function preceded with an “_” as a vector.
//we declare a function with an unlimited number of arguments
function
test(int i,...) {
//the other
arguments are store in: _test as a vector
println("Arguments:",_test);
return;
}
//We call our function
println("Test:",test(14,18,90));
Arguments:
[18,90]
Functions can
also be declared with two specific flags that are inserted before the function keyword: private and strict.
If you wish to
use both flags in the same definition, private
should precede strict.
When a function is declared private, then it cannot be seen from
outside the current KiF file. If a KiF program is loaded from within another KiF
program, private functions are
unreachable from the loader.
//This function is invisible from
external loaders…
private function toto(int i) {
i+=10;
return(i);
}
By default, when a function is declared
in KiF, the language tries to convert each argument from the calling function
into the parameters expected by the function implementation. However, this
mechanism might be a bit too loose in certain cases when a stricter parameter
checking is required. The strict flag
helps solve this problem, since any function declared with this flag will
demand strict parameter control.
strict function teststrictdeclaration(int s, int v) {
println("Int:",s,v);
}
//we declare our variables
string s1="s1";
string s2="s2";
//In this case, the system will
fail to find the right function for these parameters and will return an error
message…
teststrictdeclaration(s1,s2); //No string implementation
A frame is a
class description which is used to declare complex data structures together
with functions.
•
Member variables can be instantiated
within the frame.
•
A method _initial can be declared,
which will be automatically called for each new instance of this frame.
•
A
sub-frame is declared into the frame body. It automatically inherits the
methods and variables from the top frame.
•
Redefinition of a function is possible within
a sub-frame.
•
Private functions and variables can
also be declared within a frame.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
string
s="initial";
function
display() {
print("IN MYFRAME:"+s+"\n");
}
frame
mysubframe {
function
display() {
print("IN MYSUBFRAME:"+s+"\n");
}
}
}
A frame object
is declared with the name of its frame as a type.
myframe first; //we
create a first instance
mysubframe subfirst; //create
a sub-frame instance
//We can recreate a new instance
first=myframe; //equivalent
to “new myframe” in C++ or in Java
//To run a frame’s function
myframe.display();
A creator
function can be associated to a frame. This function is automatically called
when a new instance of that frame is created.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
string
s="initial";
function
_initial(int ij) {
i=ij;
}
function
display() {
print("IN MYFRAME:"+s+"\n");
}
}
// A new instance of myframe is
created:
myframe second(10); //the
parameters are then passed to the _initial function as in C++
The _final function is called whenever a
frame object is deleted by the garbage collector. Usually, an object which is
declared in a function or in a loop is deleted once this function or this loop
ends.
·
This function has no parameters.
·
A call to that function does not delete the object.
·
The content of this function cannot be
debugged as it is called from within the garbage collector, independently from the rest of the code.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
string
s="initial";
function
_initial(int ij) {
i=ij;
}
function
_final() {
print("IN MYFRAME:"+s+"\n");
}
}
int i=10;
while (i>=0) {
// A new
instance of myframe is created:
//At the end
of each iteration the _final function will be called
myframe item(i);
i--;
}
When items are
declared within a frame, the call to the _initial
function is done from the TOP down to its children.
Furthermore, if
an item within a frame F is instantiated within the _initial function of that frame F, then this declaration takes
precedence to any other declarations.
//We declare two frames
frame within {
int i;
//with a specific constructor function
function
_initial(int j) {
i=j*2;
println("within _initial",i);
}
}
//This frame declares a specific
"within" frame
frame test {
int i;
//In this case, we declare a
specific frame, whose declaration depends on the variable i
within
w(i);
//Our function _inital for that frame...
function
_initial(int k) {
i=k;
println("test _initial",k);
}
}
//we create a test instance: t1
with as initial value: 20
test t1(20);
The execution
yields the following result:
test
_initial 20
within
_initial 40
As we can see
on this example, the _initial function from
test was executed first. The call to _initial
in within, was done after the
execution, enabling the system to take advantage from the value of “i”, which was declared in the frame description.
However, if one
wants to initialize a frame element
with a much more complex arrangement, it is possible to create the value from
within the _initial function. In that
case, any other declaration is useless.
//This frame declares a specific
"within" frame
frame test {
int i;
//In this case, we declare a
specific frame, whose declaration depends on the variable i
within
w(i);
//Our function _inital for that frame...
function
_initial(int k) {
i=k;
//we replace the
previous description with a new one
//this
declaration subsumes the other one above
w=within(100);
println("test _initial",k);
}
}
//we create a test instance: t1
with as initial value: 20
test t1(20);
The execution
yields the following result:
test
_initial 20
within
_initial 200
As we can see
on this example, the explicit initialization of “w” in _initial replaces the declaration “within w(i);”, which becomes
redundant.
We have seen that it was possible to
create a frame element by either declaring its initialization directly into the
frame field list or within the constructor itself. When the frame element
construction is made in the constructor, a simple declaration suffices; any
other declaration would be redundant.
//This frame declares a specific "within"
frame
frame test {
int i;
//In this case, we postpone the
actual creation of the element to the constructor: _initial
within
w;
//Our function _inital for that frame...
function
_initial(int k) {
i=k;
//we replace the
previous description with a new one
w=within(100);
println("test _initial",k);
}
}
//we create a test instance: t1
with as initial value: 20
test t1(20);
If constructor parameters are required
for “w”, and no creation of that element “w” is done in the constructor, then
KiF will yield an error about missing parameters.
KiF provides a very simple way to
declare class variables. A class variable is a variable, whose value is shared
across all instances of a given frame.
frame myframe
{
common
int i;
//every
frame will have access to the same common instance of that variable.
}
myframe t1;
myframe t2;
t1.i=10;
t2.i=15;
println(t1.i,t2.i); //display for both variables : 15 15
Certain functions or variables can be
declared as private in a frame. A private function or a private variable can only be accessed
from within the frame.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
//private variable
private
string s="initial";
function
_initial(int ij) {
i=ij;
}
//private
function
private
function modify(int x) {
i=x;
s="Modified with:"+x; //you can modify “s” here
}
function
display() {
modify(1000);
//you can call “modify” here
print("IN MYFRAME:"+s+"\n");
}
}
myframe test;
//Illegal instructions on private frame members…
test.modify(100); //this instruction is illegal
as “modify” is private
println(test.s); //this instruction is illegal as “s” is
private
KiF enables the
programmer to enrich or sub-frame an
existing frame. A frame description can
be implemented in a few steps. For instance, one can start a first description,
then decides to enrich it later in the program.
//We start with a limited
definition of a frame…
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
}
//We add some code here after…
…
//Then we enrich this frame with
some more code
//All we need is to use the same
frame instruction as above, adding some new stuff
frame myframe
{
function
display() {
println(i);
}
}
Functions can
also be pre-declared, and their body can then be defined later.
//We start with a limited
definition of a frame…
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
function display(); //we prepare a display function
implementation
}
//We add some code here after…
…
//Then we enrich this frame with
some more codes
//All we need is to use the same
frame instruction as above, adding some new stuff
frame myframe
{
function
display() { //We actually implement it…
println(i);
}
}
This is
especially useful, if you want two frames to use methods from each others.
…
//If we want to add some
sub-frames…
frame myframe
{
//We can now add our sub-frame…
frame
subframe {…}
}
If you need to use the definition of
the parent frame, instead of the current thread, KiF provides a mechanism,
which is very similar to other languages such as C++ or Java. The function name
must be preceded with the frame name together with “::”.
//essai d'appel de subframes...
//We define a test frame, in which we define a subtest frame
frame test {
int i;
function _initial(int
k) {
i=k;
}
function display() {
println("In
test",i);
}
frame subtest {
string x;
function display() {
println("In subtest",i);
test::display();//will call the other display
definition from test
}
}
}
//We create two objects
test
t(1);
subtest
st(2);
//We then call the different methods
t.display();
//display:"In
test,1"
st.display();//display"In
subtest,2" and "In test,2"
st.test::display();
//display "In
test,2"
The developer
can enrich the frame with specific functions that will be used to cast a frame into another value. The
most common one is string() which
returns the string interpretation of given frame. However KiF provides every
single method to cast into a float,
an int, a Boolean, a map or a vector, but also into another frame.
Implementing a cast function is quite simple. It is a
simple function whose name is the cast itself.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
//private variable
string
s="initial";
function
string() { //a string cast
return(s);
}
function
int() { //an integer cast
return(i);
}
}
In the case of
a cast between two frames, the name of the function should be the name of that
frame.
frame frameone {
int i=10;
//every new frame will instantiate i with 10
}
frame frametwo {
int j=100;
//every new frame will instantiate i with 10
function
frameone() { //we define our cast from frametwo into frameone
frameone f;
f.i=j;
return(f);
}
}
This cast will
be used in an instantiation or in a function call to transform on the fly a
given frame into another one.
KiF also
provides a way to help define specific comparison functions between different
frame elements. These functions have a specific name, even though they will be
triggered by the following operators: “>”,”<”,”==”,”!=”, “<=” and
“>=”.
Each function
has one single parameter which is compared with the current element.
Below is a list
of these functions:
1.
equal: function ==(frame b);
2.
different: function !=(frame
b);
3.
inferior: function <(frame
b);
4.
superior: function >(frame
b);
5.
inferior equal: function <=(frame
b);
6.
surperior equal: function >=(frame b);
Each of these functions should return true or false according
to their test.
//implementation of a comparison
operator in a frame
frame comp {
int k;
//we implement the inferior operator
function
<(autre b) {
if (k<b.k)
return(true);
return(false);
}
}
//we create two elements
comp one;
comp two;
//one is 10 and two is 20
one.k=10; two.k=20;
//one is inferior to two and the
inferior method above is called
if (one < two)
println(“OK”);
KiF provides
also a mechanism to implement specific functions for the different numerical
operators. These functions must have two operators, except for ++ and --. They
must return an element of the same frame as its arguments.
1. plus: function +(frame a, frame b);
2.
minus: function
-(frame a, frame b);
3.
multiply: function *(frame a, frame b);
4.
divide: function /(frame a, frame b);
5.
power: function
^^(frame a, frame b);
6.
shift left: function <<(frame a, frame b);
7.
shift right: function >>(frame a, frame b);
8.
mod: function %(frame a, frame b);
9.
or: function |(frame a,frame b);
10. xor: function ^(frame a,frame b);
11. and: function
&(frame a,frame b);
12. “++”: function
++();
13. “--”: function
--();
frame test {
int
k;
function
++() {
k++;
}
//it is important to create a new value, which is returned
by the function
function
+(test a,test b) {
test
res;
res.k=a.k+b.k;
return(res);
}
}
test a,b,c;
c=a+b; //we will
then call our plus implementation
above.
It is also
possible to use a frame object as a vector or a map. It is then possible to
access elements through an interval or set a value through an index. To use an
object in this way, the developer must implement the following function:
1.
function [](self idx,self value): This function inserts an element in a vector
at position idx
2.
function [](self idx): This function returns the value at position
idx.
3.
function [:](self left,self right): This function returns the values between the
position left and right.
frame myvect {
vector
kj;
//This function inserts a value in the vector at position
idx
function
[](int idx,self value) {
kj[idx]=value;
}
//This function returns the value at position idx
function
[](int idx) {
return(kj[idx]);
}
//This function returns the value between l and r.
function
[:](int l,int r) {
return(kj[l:r]);
}
}
myvect test;
test[0]=10; //we call function [](…)
test[1]=5; //we call function [](…)
//we call function [:](…)
println(test[0],test[1],test[0:]); //Display: 10 5
[10,5]
Frames can be declared
with associate functions that are
called each time a value in a frame is modified. In the case of a frame, three
cases can occur:
1.
The associate function is defined at
the frame level
2.
The associate function is defined with
a frame variable
3.
The associate function is defined at
the field level
If an associate function is declared at the frame level,
then it is automatically superseded by any functions declared at the variable
level, which in turn are superseded by any functions declared at the field
level.
//Next is a test on frames and
associate functions.
frame testcallwith;
function calllocal(testcallwith tx,int before,int after)
{
println("LOCAL",tx,before,after);
}
function callframe(testcallwith tx,int before,int after)
{
println("FRAME",tx,before,after);
}
//We declare a frame with an
associate function callframe
frame testcallwith with callframe {
//a local associate function
int i with calllocal=10;
int j=30;
function
string() {
return(i);
}
}
function callvariable(testcallwith tx,int before,int after)
{
println("VARIABLE",tx,before,after);
}
//this variable is associated
with an associate variable function
testcallwith callt with callvariable;
testcallwith callt2;
callt.i=15; //This
modification will trigger calllocal associated with i
callt2.i=20; //This
modification will trigger calllocal associated with i
callt.j=15; //This
modification will trigger callvariable associated with callt
callt2.j=20; //This
modification will trigger callframe associated with testcallwith frame
declaration
LOCAL 10 10 15 //modification of callt.i
LOCAL 10 10 20 //modification of callt2.i
VARIABLE 15 30 15 //modification of callt.j
FRAME 20 30 20 //modification of callt.j
The variable
_KIFMAIN is a specific case of a frame variable: it is the main frame variable
of KiF, the one that holds the dictionary in which every single global variable
and global function is stored.
_KIFMAIN can be
used to create global variable on the fly or to remove a specific instance of
an object in memory.
_KIFMAIN works a limited map, which means that new items, which
are added to the main memory, are simply added through a simple key string: their actual name.
//We create a new instance:
int i;
_KIFMAIN["TOTO"]=i;
_KIFMAIN also exposes the method:
“pop”, which can be used to remove an instance of a given variable from memory.
//We delete it:
_KIFMAIN.pop("TOTO");
·
A map, _KIFMAIN returns a {key:value…},
where key is an item name and value its type.
·
A string, it still returns the above
map.
_KIFMAIN can be used as an object
broker in a server to create new objects on the fly with a given name.
//We create a test frame
frame test {
int i;
function _initial(int v)
{
i=v;
}
function
Value() {
return(i);
}
}
//This function creates a new
global frame element of type: test
function create(string n,int i) {
test
t(i);
_KIFMAIN[n]=t;
}
//We create a new instance:
create("TOTO",10);
//It will be possible to refer to
that variable through:
println(_KIFMAIN["TOTO"]->Value());//Display: 10
The way a variable is handled depends on its context of utilisation. Thus, when two
variables are used together through an operator, the result of the operation
depends on the type of the variable on
the left, the one that introduces the operation. In the case of an
assignation, the type of the receiving variable decides on the type of the
whole group.
If we declare two variables, one string and one integer, then the “+” operator will act as a concatenation or an
arithmetic operation.
int i=10;
string s="12";
i=s+i;
//the s is automatically converted into a
number.
print("I="+i+"\n");
Run
I=22
int i=10;
string s="12";
s=s+i;
//the i is automatically converted into a
string.
print("S="+s+"\n");
Run
S=1210
This notion of context is very
important as it defines how each variable should be interpreted. Implicit
conversions are processed automatically for a certain number of types. For
instance, an integer is automatically transformed into a string, with as value
its own digits. In the case of a string, the content is transformed into a
number if the string only contains digits, otherwise it is 0.
For more specific cases, such as a
vector or a map, then the implicit conversions are sometimes a bit more
complex. For instance, a vector as an integer will return its size and as a
string a representation of this vector. A file as a string returns its filename
and as an integer, its size in bytes.
In the case of a frame, the conversion
must be explicitly provided by the user. It consists in adding a specific
function whose name matches one of the following types: string, int, float, long, boolean, vector or map.
frame myframe
{
int i=10;
//every new frame will instantiate i with 10
string
s="initial";
function
initial(int ij) {
i=ij;
}
function
int() {
return(i);
}
function
string() {
return(s);
}
}
myframe test(10);
//print automatically converts
each parameter into a string
print("MYFRAME:",test,"\n");
Run
MYFRAME: initial
KIF provides many different objects,
each with a specific set of methods. Each of these objects comes with a list of
predefined methods.
All the types
below share the same basic methods:
a)
isa(typename): check if a variable has the type: typename (as a string)
b)
type(): return the type of a variable as
a string.
c)
methods(): return the list of methods
available for a variable according to its type.
d)
infos(string name): return a help about a specific method.
e)
string(): return the string interpretation
of a variable
f)
int(): return the integer interpretation
of a variable
g)
float(): return the float interpretation
of a variable
h)
vector(): return the vector interpretation
of a variable
i)
map(): return the map interpretation of
a variable
Self is
a transparent object, similar to a sort of pointer, which does not require any
specific transformation for the parameter, when used in a function.
function compare(self x, self y) {
if (x.type()==y.type())
print("This is the same sort of objects");
}
//For instance, in this case, the
function compare receives two
parameters, whose types might vary. A self
declaration removes the necessity to apply any specific conversion to the
objects that are passed to that function.
string s1,s2;
compare(s1,s2);
//we compare two frames
myframe i1;
myframe i2;
compare(i1,i2);
The XIP regular expression formalism has been borrowed from
XIP and can be used freely within KiF. It can be used independently from XIP.
A XIP regular
expression is a string where meta-characters can be used to introduce a certain
freedom in the description of a word. These meta-characters are the following:
%d stands for any digit
%p stands for any punctuation belonging to
the following set:
<
> { } [ ] ) , ; : . & | ! / \ = ~
# @ ^ ? + - * $ % ' _ ¬ Ł €` “
%c stands for any lower case letter
%C stands for any upper case letter
? Stands for any character
%? Stand for the character “?” itself
Example:
dog%c matches dogs or dogg
m%d matches m0, m1,…,m9
A regular expression can use the
Kleene-star convention to define characters that occurs more than once.
x*: the
character can be repeated 0 or n times
x+: the
character must be present at least once
(x): the
character is optional
([x,…,x]*,+): defines a character that can have more than one property
where x is a character or a
meta-character. There is one special case with the ‘*’ and the ‘+’. If the
character that is to be repeated can be any character, then one should use “%+” or “%*” .
These two rules
are also equivalent to “?*” or “?+”.
1) a*ed matches aed, aaed, aaaed
etc. the a can be present 0 or n times)
2)
a%*ed matches
aed, aued, auaed, aubased etc. any characters can
occur between a and ed)
3)
a%d* matches
a, a1, a23, a45, a765735 etc.
4)
a[%d,%p] matches a1,
a/, a etc.
5)
a[bef] matches
ab, ae or af.
6)
a[%d,bef] matches a1,
ab, ae, af, a0, a9 etc.
7)
a[be]+ matches
ab, ae, abb, abe, abbbe, aeeeb etc.
The string type is used to handle any sorts
of string. It provides many different methods to extract a substring, a
character or applies any pattern recognition on the top of it.
In the following methods, rgx follows the XIP regular expression
formalism (see the chapter dedicated to these expressions).
1.
byteposition(int pos): convert a character position into a byte position (especially useful in
UTF8 strings)
2.
bytes(): return a ivector of bytes
matching the string.
3.
charposition(int pos): convert a byte position into a character position (especially useful in
UTF8 strings)
4.
deaccentuate(): Remove the accents from accented characters
5.
evaluate(): evaluate the meta-characters
within a string and return the evaluated string (see below).
6.
extract(int pos,string from, string
up1,string up2...): return a svector containing all substrings from the current string,
starting at position pos, which are composed of from up to one of the next strings up1, up2,...
up1..upn.
7.
fill(int nb,string char): create a string of nb chars.
8.
find(string sub,int pos): Return the position of substring sub starting at position pos
9.
format(p1,p2,p3): Create a new string from the current string in which each '%x' is
associated to one of the parameters, 'x' being the position of that parameter
in the argument list. 'x' starts at 1.
10. get():
Read a string from keyboard.
11. geterr():
Catch the current error output. Printerr and printlnerr will be stored in
this string variable.
12. getstd():
Catch the current standard output. Print and println will be stored in
this string variable.
13. html():
Return the string into an HTML compatible string or as a vector of
strings
14. insert(i,s): insert the string s at i. If i is -1, then insert s between each
character in the input string.
15. isalpha():Test if a string only contains only alphabetical characters
16. isconsonant(): Test if a string only contains consonants
17. isdigit(): Test if a string only contains digits
18. islower(): Test if a string only contains lowercase characters
19. ispunctuation(): Test if the string is composed of punctuation signs.
20. isupper():Test if a string only contains uppercase characters
21. isutf8(): Test if a string contains utf8 characters
22. isvowel(): Test if a
string only contains only vowels
23. last(): return last character
24. latin(): convert an UTF8 string in LATIN
25. left(int
nb): return the first nb characters of a string
26. levenshtein(string
s): Return the edit distance with s according to Levenshtein algorithm.
27. lisp():
Convert a parenthetic expression into a vector (see below)
28. lisp(string
opening,string closing): Convert a recursive expression
using opening and closing characters as separators (see below)
29. lower(): Return the string in lower characters
30. mid(int
pos,int nb): return the nb characters
starting at position pos of a string
31. ngrams(int
n): return a vector of all ngrams of rank n.
32. ord(): return the code of a string character. Send either the
code of the first character or a list of codes, according to the type of the
receiving variable.
33. parse():
Parse a string as a piece of code and returns the evaluation in a vector.
34. pop(): remove last character
35. pop(i): remove character at position i
36. regex(rgx): Return all substrings matching rgx
37. regex(rgx): Return the position of the substring matching rgx in the string
38. regex(rgx): Return the substring matching rgx in the string
39. regex(rgx): Test if the regular expression rgx applies to string
40. regexip(rgx): Return all substrings matching rgx
41. regexip(rgx): Return the position of the substring matching rgx in the string
42. regexip(rgx): Return the substring matching rgx in the string
43. regexip(rgx): Test if the regular expression rgx applies to string
44. removefirst(int
nb): remove the first nb characters of a string
45. removelast(int
nb): remove the last nb characters of a string
46. replace(sub,str): Replace the substrings matching sub with str
47. replaceregexip(rgx,str): Replace the substrings matching rgx with str
48. replacergx(rgx,str): Replace the substrings matching rgx with str
49. reverse(): reverse the string
50. rfind(string
sub,int pos): Return the position of substring sub backward
starting at position pos
51. right(int
nb): return the last nb characters of a string
52. size(): return the length of a string
53. split(string
splitter): split a string along splitter and store the
results in a svector (string vector). If
splitter==“”, then the string is split into a vector of characters. If splitter
is not provided, then the string is split along space characters...
54. splitrgx(rgx): Split string with regular expression rgx. Return a svector of
substrings. Need regular expression operator (…) to keep substrings.
55. tags(string
o,string c): Parse a string as a parenthetic
expression, where the opening and closing strings are provided
56. tokenize(bool
comma,bool separator,bool keepwithdigit): Tokenize a string into words and
punctuations. If comma is true, then the “,” is the decimal
separator, otherwise it is the “.”. If ‘separator’ is true, then ‘.’ or ‘,’ can
be used as separators as in: “3,000.10”. keepwithdigit enables numbers to be concatenated with the strings next to them as in
“3G”. tokenize returns a svector.
Each of these parameters is optional. When one of these parameters is omitted,
then its default value is false.
57. stokenize(map
keeps): Tokenize a string into words and punctuations.
Keeps is used to keep together specific strings.
58. trim(): remove the trailing characters
59. trimleft(): remove the trailing characters on the left
60. trimright(): remove the trailing characters on the right
61. upper(): Return the string in upper characters
62. utf8(): convert a LATIN string into UTF8
If you use strings declared between “”,
then KiF will automatically recognize the following meta-characters:
·
\n, \r and \t which are the line feed,
the carriage return, and the tabulation respectively.
KiF also recognizes another large set
of meta-characters, which are automatically translated for you when you use the
method “evaluate”:
·
Decimal code: \ddd, which is then
translated into the Unicode character of that code: \048 is for instance the
character ‘0’.
·
Hexadecimal code: \xhh, which is also
translated into the corresponding Unicode character: \x30 is the character ‘0’.
·
Unicode code: \uhhhh, which is also
translated into the corresponding Unicode character: \u0030 is the character
‘0’.
·
&#d(d)(d)(d); which is also
translated in the corresponding Unicode character:  is the character
‘0’. This coding occurs in XML and HMTL texts.
·
&namecode; for which a long list of
equivalence exists (XML and HTML again). For instance: &eaccute; is the
character: é.
Conversely, the method “html” returns a
string in which non ASCII character are translated into HTML encoding.
sub in s: test
if sub is a substring of s
for (c in s) {…}: loop
among all characters. At each iteration, c contains a character from s.
+: concatenate
two strings.
“…”: define a string, where meta-characters
such as “\n”,”\t”,”\r”,”\”” are interpreted.
‘…’: define a string, where
meta-characters are not interpreted. This string cannot contain the character “’”.
str[i]: return
the ith character of a string
str[i:j]: return
the substring between i and j. i and j can be substrings, which the system will use to
extract the substring.
str[s..]: return the
substring starting at string s.
str[-s..]: return the
substring starting at string s. In this case, s is searched from the end of the string.
N.B. When
i and j are positive integers, they are treated as absolute
positions within the string. However, when the values are negative, they are
considered as offsets to be counted from each string extremities.
However, if the first element of the interval is a substring and the second one
is a positive integer, then this second index will be treated as an offset from
the rightmost position of where the substring was found.
You can also modify a character range.
string s="This is a cliché, which contains a 'é'";
s[10:16] cliché //absolute positions
s["cliché":7] cliché, which //offset from end of substring
s["cliché":-4] cliché, which contains a //offset from end of string
s[-"a":] a 'é' //looking for last instance of a
s[-"a":]="#" This is a cliché, which contains # //replacing a substring
If an index
is out of bounds, then an exception is raised unless the flag erroronkey has
been set to false. In that case, KiF will return empty.
If the string contains digits, then it
is converted into the equivalent number, otherwise its conversion is 0.
KiF also
provides a way to decipher parenthetic expressions such as:
( (S (NP-SBJ Investors)
(VP are
(VP appealing
(PP-CLR to
(NP-1 the Securities))
(S-CLR (NP-SBJ *-1)
not
(VP to
(VP
limit
(NP (NP their access)
(PP
to
(NP (NP information)
(PP
about
(NP (NP stock purchases)
(PP
by
(NP "insiders”)
))))))))))).))
KiF
provides a method: lisp which takes
as input a structure as the one above and translates it into a vector.
vector v=s.lisp();
//s contains a parenthetic expression as above
The
second function enables the use of different opening or reading characters.
KiF can
analyze the structure below:
<
<S <NP-SBJ They>
<VP make
<NP the argument>
<PP-LOC in
<NP <NP letters>
<PP to
<NP the agency>> > > > >
.>
with the following instruction:
vector
v=s.lisp('<','>');
tags is similar to
the lisp method except that instead
of characters, it takes strings as input.
You should not use this method to parse XML output, use xmldoc instead.
string s="OPEN This is OPEN a nice
OPEN example CLOSE CLOSE CLOSE";
vector v=s.tags('OPEN','CLOSE');
Output: v=[['this', 'is', ['a','nice', ['example']]];
//Below are some examples on
string manipulations
string s;
string x;
vector v;
//Some basic string manipulations
s="12345678a";
x=s[0]; // value=1
x=s[2:3]; // value=3
x=s[2:-2]; //value=34567
x=s[3:]; //value=45678a
x=s[:"56"]; //value=123456
x=s["2":"a"]; //value=2345678a
s[2]="ef"; //value=empty
//The 3 last characters
x=s.right(3); //value=78a
//A split along a space
s="a b
c";
v=s.split("
"); //v=["a","b","c"]
//regex, x is a string, we look
for the first match of the regular expression
x=s.regexip("%d%d%c");
//value=78a
//We have a pattern, we split our
string along that pattern
s=’12a23s45e’;
v=s.regexip("%d%d%c"); //
value=['12a','23s','45e']
x=s.replaceregexip("%d%ds","X"); //value=12aX45e
//replace also accepts %x
variables as in XIP regular expressions
x=s.replaceregexip("%d%1s","%1"); //value=12a2345e
//REGULAR REGULAR EXPRESSIONS:
Not available on all platforms
string rgx='\w+day';
string str="Yooo
Wesdenesday Saturday";
vector vrgx=str.regex(rgx); //['Wesdenesday','Saturday']
string s=str.regex(rgx); //Wesdenesday
int i=str.regex(rgx); // position is 5
//We use (…) to isolate specific
tokens that will be stored in the
//vector
rgx='(\d{1,3}):(\d{1,3}):(\d{1,3}):(\d{1,3})';
str='1:22:33:444';
vrgx=str.splitrgx(rgx); // [1,22,33,444]
str='1:22:33:4444';
vrgx=str.splitrgx(rgx); //[] (4444
contains 4 digits)
str="A_bcde";
if (str.regex('[a-zA-Z]_.+')) //Full match
required
println("Yooo"); //Yooo
str="ab(kif12,kif14,kif15,kif16)";
vector v=str.extract(0,"kif",",",")"); //Result:
[‘12’, 14’,’ 15’,’ 16’]
string frm="this
%1 is a %2 of %1 with %3";
str=frm.format("tst",12,14);
println(str); //Result: this tst is a 12 of tst
with 14
KiF provides three different numerical: int, float and fraction,
which is described in the next section.
int
and float have been implemented respectively
as a long and a double. long is implemented as a 64 bits integer, respectively a
__int64 on Windows or a “long long” on Unix platforms.
1.
#(): return the bit complement
2. atan(): arc
tangent
3.
chr(): return
the ascii character corresponding to this number as a code.
4. cos(): cosinus
5.
exp(): return the exponential
6.
factors(): return the prime factor decomposition
as an ivector.
7.
format(string
form): return a string formatted
according to the pattern in form. (this format is the same as the sprintf
format in C++)
8.
fraction(): return the value as a fraction.
9.
get(): Read a number from keyboard
10. ln(): return the neperian log
11. log(): return the log base 10 of the value
12. sin(): sinus
13. sqrt(): return the square root
14. tan(): tangent
A
hexadecimal number always starts with “0x”. It is considered by KiF as a valid
number as long as it is a valid hexadecimal string. A hexadecimal declaration
can mix upper or lower characters from the hexadecimal digits: A,B,C,D,E,F.
+,-,*,/: mathematical operators
<<,>>,&,|,^: bitwise
operators
%: division
modulo
^^: power
(2^^2=4)
+=,-= etc: self
operators
float f;
int i=10;
int j=0xAb45; //Hexadecimal number
f=i.log(i); //value= 1
f+=10; //value=11
f=i%5; //value=0
The bit type
implements a vector of bits, which can be used as way to store numerous Boolean
values. A bit vector can be transformed
into a vector or a map of integers. It can also be iterated. It can also take
as a value, an integer, a float or a long.
When a bit
vector is built out of an integer, a float or a long, the bit representation
depends on the platform implementation of these values, which might be
different from one machine to another.
1.
#(value): returns the complement of the bit vector
2. bit vb(nbbits): creates a bit vector of size nbbits. Nnbits defines the size in bits of the bit vector. However, since bits are
stored in blocks of sixteen bits, the actual size might be larger than the one
chosen. For instance, bit v(25) will generate a 32 bits vector.
<<,>>,&,|,^: bitwise
operators. The “<<” operator (shift left) might extend the length of your
bit vector size.
It returns a hexadecimal representation
of the bit vector.
It returns a vector or a map of
integers
It returns the transformation into an
integer of the first 32 bits.
bit test1(62); //we
create two bit vectors
bit test2(64);
test1=234; //we
initialize the first one with 234
test2=654531;
int i=test2;
//we transform this bit vector into an
integer…
vector v=test1; //we
transform our bit vector into a vector of integer.
test1=test1 & test2; //we compute the binary AND between test1 and test2
test1<<=7; //shift
left
test1=#(test1);
// we compute the bit complement of our bit vector
test1[1]=true;
//we modify the first bit of our bit vector
println(test1[0],test1[1],test1[2],test1[3]);
//we display some bit values
The bits type
implements a map of bits, which can be used as way to store numerous Boolean
values. A bits map can be transformed
into a map of integers. It can be iterated. It can also take as a value, an
integer, a float or a long.
The type bits implements
a sparse representation of bits, to the difference of the type bit which implements a vector of all
bits. Furthermore, bits are stored on a 64
bits long in bits, while it they
are stored over a short in bit.
If we declare
the two following variables:
bit vbit;
bits mbit;
vbit[120]=1;
//Then internally we create 120/16=8 shorts to handle all the necessary bits.
The last short has the 8th bit set to 1.
mbit[120]=1
//Then internally we create only one element, whose key is 1 (=120/64), and
whose bit 56 is set to 1.
1.
#(value): returns the complement of the bit vector
&,|,^: bitwise
operators.
It returns a hexadecimal representation
of the bit vector.
It returns a map of integers.
It returns the transformation into an
integer of the first 32 bits.
bits test1(62); //we
create two bit vectors
bits test2(64);
test1=234; //we
initialize the first one with 234
test2=654531;
int i=test2;
//we transform this bit vector into an
integer…
map m=test1; //we
transform our bit vector into a vector of integer.
test1=test1 & test2; //we compute the binary AND between test1 and test2
test1=#(test1);
// we compute the bit complement of our bit vector
test1[1]=true;
//we modify the first bit of our bit vector
println(test1[0],test1[1],test1[2],test1[3]);
//we display some bit values
KiF enables users to handle numbers as
fractions, which can be used anywhere in any calculations. All the above
mathematical methods for integers and floats are still valid; however this type
offers a few other specific methods.
1.
d(): return the denominator of the
fraction
2.
d(int
v): set the denominator of the
fraction
3.
fraction
f(int n,int d): a fraction can be created with providing a numerator and a denominator.
By default, the numerator is 0 and the denominator is 1.
4.
invert():
switch the denominator with the
numerator of a fraction
5.
n(): return the numerator of the
fraction
6.
n(int
v): set the numerator of the
fraction
7.
nd(int
n,int d): set the numerator and
denominator of a fraction
KiF automatically creates the
appropriate float or integer, through a simple computing of the fraction. This
translation results in most of the cases into a loss of information.
Furthermore, at each step, KiF simplifies the fraction in order to keep it as
small as possible.
As a string, KiF returns: “NUM/DEN”
//we create two fractions
fraction f(10,3);
fraction g(18,10);
//we add g to f...
f+=g;
println(f); //Display:
77/15
A vector is used to store any objects, whatever their type.
It exposes the following methods.
1. apply(a,b,c…):
apply all functions passing a,b,c etc.
as parameters.
2. apply(function,a,b,c): apply function to all elements in the vector, with the
current element being the first parameter of the function, and a,b,c etc…
the next parameters.
3. bytes():
return the string matching the bytes
stored in the vector. The vector should only contains integers between 0..255.
4. clear():
clean the vector
5. editdistance(vector v)
: edit distance between two vectors
6. insert(i,x):
insert the element x at position i
7. join(string
sep): concatenate each element in the vector
in a string where each element is separated from the others with sep.
8. json():
return a json compatible string
9. last():
return the last element of the vector.
10. merge(vector
v): merge a vector v into the current
vector.
11. move(int
pfrom,int pto): move an element from pfrom to pto
12. permute(): permute the values in a vector. Return
true, if other permutations are still available.
13. pop():
remove the last element from the
vector.
14. pop(int
i): remove the ith element from the vector.
15. product():
Multiply each element with the others
16. push(a):
add a to the vector
17. range(first,last):
generate a vector of elements from first to last,
with a step of 1. The type of first and last defines the content of the vector.
18. range(first,last,step):
generate a vector of elements from first to last,
with a step of step. The type of first and last defines the content
of the vector.
19. reserve(int nb):
reserve nb characters in the vector
20. reverse():
reverse the order of the elements in
the vector
21. size(): return
the length of the vector
22. shuffle(): reshuffle
values in the vector
23. sort(compare):
sort the content of the vector
according to compare
function.
24. sortfloat(bool
order): sort the content of the vector, forcing each value
to be a float. order==false order is increasing, order=true order is decreasing.
25. sortint(bool
order): sort the content of the vector, forcing each value
to be a int. order==false order is increasing, order=true order is decreasing.
26. sortlong(bool
order): sort the content of the vector, forcing each value
to be a long. order==false order is increasing, order=true order is decreasing.
27. sortstring(bool
order): sort the content of the vector, forcing each value
to be a string. order==false order is increasing, order=true order is
decreasing.
28. sum():
Sum each element with the others
29. test(int
i): test
if i is a valid slot in the vector
30. totext():
return the string matching the bytes
stored in the vector. The vector should only contains integers between 0..255.
31. unique():
return a vector where duplicates have
been removed
A vector can be initialised with a
structure between “[]”.
vector v=[1,2,3,4,5];
vector vs=["a","b","v"];
vector vr=range(10,20,2); // vr is initialized with [10,12,14,16,18];
vs=range(‘a’,’z’,2); //vr is
initialized with ['a','c','e','g','i','k','m','o','q','s','u','w','y']
x in vect:
return true or a list of indexes, according to the receiving variable. If the
vector contains strings, then the system will return true or its index, only if the value is
the same string as the one tested. A in is not PERFORMED in this case
within the local strings.
for (s in vect) {…}: loop
among all values. At each iteration “s” contains a value from vect.
+,*,-,/ etc..: add
etc.. a value to each element of a vector or add each element of a vector to
another
&,|:
intersection or union of two vectors
&&&:
merge a vector with a value
:: : insert a value
in a vector.
10::[1,2,3] ŕ [10,1,2,3]
[1,2,3]::10 ŕ [1,2,3,10]
It returns the size of the vector
It returns a structure, where each
element is separated from the others with a comma, similar to the structure
used to initialize a vector.
str[i]: return
the ith character of a vector
str[i:j]: return
the sub-vector between i and j.
KiF
provides a very peculiar method to benefit from a vector. You can use a vector pattern
of the form: [a1,..,an|tail], where a1,..,an,
tail are variables or values. The tail is the rest of the vector, once each
variable has been assigned.
These vector
patterns can be used in two ways:
o
In
assignment:
·
[a,b|v]=[1,2,3,4,5],
then a=1, b=2 and v=[3,4,5]
o
In
for..in loops
·
for
([a,b|v] in [[1,2,3,4],[3,4,5]]) etc…
In the first iteration, a=1,b=2 and v=[3,4]
In the second iteration, a=3,b=4 and v=[5]
vector vect;
vect=[1,2,3,4,5];
print(vect[0]); //display: 1
print(vect[0:3]); //display: [1,2,3]
vect.push(6);
print(vect); //display: [1,2,3,4,5,6]
vect.pop(1);
print(vect); //display: [1,3,4,5,6]
vect=vect.reverse();
print(vect); //display:[6,5,4,3,1]
vect.pop();
print(vect); // display:[6,5,4,3]
vect+=10;
print(vect); // display:[16,15,14,13]
//This function should return only true or false
//The type of the parameters will determine its
//behaviour
function compare(int i,int j) {
if
(i<j)
return(true);
return(false);
}
vector myvect;
iterator it;
myvect=[10,5,20];
myvect.sort(compare);
it=myvect;
for (it.begin();it.nend();it.next())
print("Content:"+it.key()+"="+it.value(),"\n");
RUN
Content:0=5
Content:1=10
Content:2=20
//This function should return only true or false
//The type of the parameters will determine its behaviour,
in this case, we //suppose each element to be a string or converted as a string.
function compare(string i,string j) {
if
(i<j)
return(true);
return(false);
}
vector myvect;
iterator it;
myvect=[10,5,20];
myvect.sort(compare);
it=myvect;
for (it.begin();it.nend();it.next())
print("Content:"+it.key()+"="+it.value(),"\n");
RUN (This time we sort out strings)
Content:0=10
Content:1=20
Content:2=5
It is possible in KiF to call apply with as a first parameter a
function or a call variable. Then,
each element from the vector can be called and modified if necessary
accordingly.
//We first implement a method,
with the first element being
//extracted from the vector
function modify(self vectorElement,string s)
{
println(s,
vectorElement);
//we modify it...: a self element is akin to a pointer
parameter passing
vectorElement+=1;
}
//We create a vector, with
numerical values
vector v=[1,2,3,4,5];
//We apply our function to each
element
v.apply(modify,"Modification");
//we print it out
println(“New:”,v);
RUN:
//modify has been called 5 times,
hence the 5 “modification” below
Modification 1
Modification 2
Modification 3
Modification 4
Modification 5
New: [2,3,4,5,6] //Each value has been modified
//x is the value from the vector,
and d a parameter passed to ‘apply’
//There are both declared as
self, so as to allow their values to be modified
function fact(self x,self d) {
x*=d;
d=x;
}
int d=1;
ivector iv=[1,2,3,4,5,6];
iv.apply(fact,d); //iv=[1,2,6,24,120,720]
A list is used to store any objects, whatever their type. It
exposes the following methods. It is different from vector in the sense that it works as a list in which, elements can
added at the front or at the back, and can be removed from the front and from
the back, allowing FIFO, LILO, FILO, or LIFO management of lists.
1.
apply(a,b,c…): apply
all functions stored in the list, passing a,b,c etc. as parameters.
2.
apply(function,a,b,c): apply function to all elements in the list, with the
current element being the first parameter of the function, and a,b,c etc…
the next parameters.
3.
clear(): clean
the list
4.
editdistance(list
v) : edit
distance between two lists
5.
first(): return
the first element of the list
6.
insert(i,x): insert
the element x at position i
7.
join(string sep): concatenate
each element in the list in a string where each element is separated from the
others with sep
8.
json(): return a json compatible string
9.
last(): return
the last element of the list
10. merge(list
l): merge
a list into the current list.
11. popfirst():
remove the first element from the list.
12. permute(): permute the values in a vector. Return
true, if other permutations are still available.
13. poplast():
remove the last element from the list.
14. product():
Multiply each element with the others
15. pushfirst(a):
add a to the beginning of the list
16. pushlast(a):
add a to the end of the list
17. reverse():
reverse the order of the elements in
the list
18. shuffle(): reshuffle
values in the list
19. size(): return
the length of the list
20. sum():
Sum each element with the others
21. test(int
i): test
if i is a valid slot in the list
22. unique():
return a list where duplicates have been removed
A list can be initialised with a
structure between “[]”.
list v=[1,2,3,4,5];
list vs=["a","b","v"];
x in vlist:
return true or a list of indexes, according to the receiving variable. If the list
contains strings, then the system will return true or its index, only if the value is
the same string as the one tested. A in is not PERFORMED in this case
within the local strings.
for (s in vlist) {…}: loop
among all values. At each iteration s contains a value from vlist.
+,*,-,/ etc..: add
etc.. a value to each element of a list or add each element of a list to
another
&,|:
intersection or union of two lists
It returns the size of the list
It returns a structure, where each
element is separated from the others with a comma, similar to the structure
used to initialize a vector or a list.
You can use indexes with list objects, as with vector. However,
indexes with lists are rather inefficient, and should be avoided.
list vlist=[1,2,3,4,5];
vlist.pushfirst(10);
vlist.pushlast(20); //display:
[10,1,2,3,4,5,20]
vlist.popfirst();//display:
[1,2,3,4,5,20]
vector v=vlist; //transform
a list into a vector
The first three
types are specialized vector containers for integers (ivector), floats (fvector)
and strings (svector).
These
containers can only store their specific type of values. They are very useful
to keep the memory consumption of these elements in check. Basically, when you
store a string in a vector, KiF needs
to create a string object, which will
be stored within your vector, since a
vector can only store objects. In the
case of a svector, the system will store the string directly
without requesting KiF to create any specific string object. The storage is
then reduced to only strings and the access is both faster and leaner.
You use these
structures exactly in the same way as a vector.
svector test;
test.push("toto");
The last type, “table”, is a container whose size must
be defined at creation, once for all. It expects integers as indexes…
table test(10);
test[1]="i";
This container is extremely fast, as it is based on a
C table implementation, however, its limitations are the ones set by its size
at creation. However, if the initial size is too small, you still can use
“resize” to enlarge or decrease that initial size.
table test(10);
println(test.size()); //10
test.resize(20);
println(test.size()); //the
size is now 20
Not only will this method modify the current size of
your table, it will also copy all previous elements in their new place. Note,
that if you actually decrease the size of the table, elements beyond the new
limit will be lost.
A map is a hash table, which uses as key any string or any
element which can be analysed as a string. The map in KiF converts any keys into a string, which basically
means that “123” and 123 are one and unique key.
treemap
is similar to map, with a difference
that keys in a treemap are
automatically sorted.
primemap
is novel sort of hash-map, where keys are organized along prime numbers. The
advantage of this map is that you can iterate along the order in which the
values were stored in the map.
1. apply(a,b,c…):
apply all functions stored in the map, using
a,b,c etc. as parameters.
2. apply(function,a,b,c): apply function to all elements in the map, with the
current element being the first parameter of the function, and a,b,c etc…
the next parameters. The basic function should contain two parameters, one for
the key, the second for the value.
3.
clear(): clean
the vector
4. editdistance(map v)
: edit distance between two maps
5.
items():return
a vector of key:value.
6.
invert(): return
a new map where keys and values are switched. The values are now the keys, and
the keys the values.
7.
join(string keysep,string sep):
merge the keys and their values into a single string. Keys are separated from their values with keysep,
while key-values are separated with sep.
8. json():
return a json compatible string
9.
keys(): returns
the map keys as a vector
10. merge(map
m): merge a map into another.
11. pop(string
key): remove the elements matching key
12. seeds(ivector
primes): only
for primemaps. Provides the list of keys that are used to create the various levels
of hash tables.
13. product():
Multiply each element with the others
14. size(): return
the length of the map
15. sum():
Sum each element with the others
16. test(string
k): test
is k is a valid key in the map
17. values():
return the values as a vector
A map can be initialised with a
description such as: {“k1”:v1,”k2”:v2...}
map toto= {“a”:1,”b”:2};
x in amap:
return true or a list of indexes, according to the receiving variable. If the
map contains string values, then the system will return true or its index, only if a value is the
same string as the one tested. A in
is not PERFORMED in this case within the local strings.
Important:
x is tested against the values of the map, not the keys. Use test
to perform a test on the keys.
for (s in amap) {…}: loop
among all keys. At each iteration “s” contains a key from amap.
+,*,-,/ etc..: add
etc.. a value to each element of a map or add each element of a map to another
along keys
&,|:
intersection or union of two maps along keys.
map[key]:
return the element whose key is key. If key is not a key from map, then return null.
Return the size of the map
Return a string which mimics the map
initialization structure.
map vmap;
vmap["toto"]=1;
vmap[10]=27;
print(vmap); //display: {'10':27,'toto':1}
There are different ways to test
whether a map possesses a specific key. The first way is to use the test operator, which will return true or false. The other way is to
catch the error when a wrong index is provided with the container.
However, it is faster and more
efficient to use test instead of the
above equality.
if (m.test("ee"))
println("ee is not a key in m ");
if you want to avoid an exception
whenever a wrong key is used, place erroronkey(false)
at the beginning of your code. In that case, an empty value will be returned instead of an exception.
if (m["ee"]==empty)
println("ee is not a key in m ");
These two
types are very similar to “map” and to “treemap” with one exception, they use integer (mapi,treemapi,primemapi) and float (mapf,treemapf,primemapf) as keys
while “map”, “treemap” and “primemap” use strings.
These types are
specialized map containers for integers (imap,itreemap,iprimemap),
floats (fmap,ftreemap,fprimemap) and
strings (smap,streemap,sprimemap).
These
containers can only store their specific type of values. They are very useful
to keep the memory consumption of these elements in check. Basically, when you
store a string in a map, KiF needs to
create a string object, which will be
stored within your map, since a map
can only store objects. In the case
of a smap, the system will store the string directly without requesting KiF to
create any specific string object. The storage is then reduced to only
strings and the access is both faster and leaner.
You use these
structures exactly in the same way as a map.
smap test;
test["toto"]="i";
These types
replace the keys, which for all other maps are strings, with integers.
Values are then indexed on integers
rather than on strings.
These
types replace the keys, which for all other maps are strings, with floats.
Values are then indexed on floats
rather than on strings.
These types are used to handle sets of elements, which
you can either store or retrieve.
Note: They are based on the STL std::set
implementation.
The first type is a general one that can be used to
store any types of data. The three others are specialized to store strings,
integers or floats.
1.
bytes(): return the string matching the bytes stored
in the set. The set should only contains integers between 0..255.
2.
clear(): clean
the set
3.
editdistance(set
v) : edit
distance between two sets
4.
join(string sep): concatenate
each element in the set in a string where each element is separated from the
others with sep.
5.
json(): return a json compatible string
6.
merge(set v): merge a set v into the current set.
7.
pop(value): remove
value from the set.
8.
product(): Multiply
each element with the others
9.
push(a):
add a to the set
10. size(): return
the length of the set
11. sum():
Sum each element with the others
12. test(value):
test if value exists in the set
A set can be initialised with a
structure between “[]”.
set v=[1,2,3,4,5];
set vs=["a","b","v"];
x in vset:
return true or a set of indexes, according to the receiving variable. If the set
contains strings, then the system will return true or its index, only if the value is
the same string as the one tested. A in is not PERFORMED in this case
within the local strings.
for (s in vset) {…}: loop
among all values. At each iteration s contains a value from vset.
+,*,-,/ etc..: add
etc.. a value to each element of a set or add each element of a set to another
&,|:
intersection or union of two sets
It returns the size of the set
It returns a structure, where each
element is separated from the others with a comma, similar to the structure
used to initialize a vector or a set.
You can use indexes with set objects, as with vector. However,
indexes in sets only checks whether a value exists.
set vset=[1,2,3,4,5];
vset.push(10);
vset.push(20);
vset.pop(10); //remove
the value 10 from the set
vector v=vset; //transform
a set into a vector
The value
containers are the specific implementation of vectors and maps for strings,
floats and integers. If you use logical operators with these containers, then
the way they function depends on the values stored in the container.
For strings,
the logical operators work as set operators. The & yields the intersection
between two string containers, the | yields the union of two string containers,
while the ^ yields the non common values between two strings.
svector sv=["a","b","c","d",'e','h'];
svector svv=['e',"f","g",'h'];
println("And:",sv&svv); ŕ ['e','h']
println("XOR:",sv^svv); ŕ ['f','g','a','b','c','d']
println("OR:",sv|svv); ŕ ['a','b','c','d','e','h','f','g']
smap
sm={"a":1,"b":2,"c":3,"d":4,'e':5,'h':6};
smap
smm={'e':5,"f":2,"g":3,'h':4};
println("And:",sm&smm); ŕ {'e':'5'} ‘h’ has a
different value…
println("XOR:",sm^smm); ŕ {'f':'2','g':'3','a':'1','b':'2','c':'3','d':'4'}
println("OR:",sm|smm); ŕ {'a':'1','b':'2','c':'3','d':'4','e':'5','h':'6','f':'2','g':'3'}
For numerical
values, the logical operators work as the other operator at the binary level,
not at the set level.
ivector iv=[1,2,3,4,5,6,7,8,9];
ivector vi=[2,4,6,8,10,12,14,16,18];
println("And:",iv&vi); ŕ [0,0,2,0,0,4,6,0,0]
println("XOR:",iv^vi); ŕ [3,6,5,12,15,10,9,24,27]
println("OR:",iv|vi); ŕ [3,6,7,12,15,14,15,24,27]
The type
“automaton” provides a very efficient way to handle word lists. A word list is
a text file, in which each line exposes one single word:
A word file
should contain lines such as the example below:
Abbeville
Abbies
Abbie
Abbies
Abbie
Abbies
Abbie
Abbis
Abbi
...
This automaton
is very efficient to find out if a word belongs to the lexicon or not. It can
store hundred of thousand of words. It can also be used in conjunction with a function to control the way the automaton
can be traversed...
It exposes
the following methods:
1.
add(string
wrd): add a word to the automaton
2.
add(string
wrd,string lemmafeature): add a word and
its lemma form in the automaton as in a transducer.
3.
dump(string pathname): dump the content of the automaton in memory into a
text file. Useful, if you have been intensively using addword.
4.
editdistance(string
wrd,int threshold,int flags): compute an
edit distance on the basis of this automaton with a threshold size. The flags is a combination
of the following action (see below for a example):
a_first: the automaton can apply actions to the first
character. If this action is not set, then all the strings that will be
compared against it will start with this character. If this character is not
present in the automaton, then the system will switch to this mode.
a_change: the automaton can change a character to another
a_delete: the automaton can delete a character
a_insert: the automaton can insert a character
a_switch: the automaton switches two characters
a_nocase: the automaton takes into account the difference in
case with the current character string to only add 0.1 to the score.
a_surface: the automaton returns the value as a surface form.
a_full: the automaton returns the surface form concatenated
with the lemma forms and features with the feature separator string. Use a
“tab” character if the feature separator string is not available.
a_split: the automaton will try to cut the string along word
boundaries, if the string is a concatenation of different words.
a_skip: The skip action is used to “skip” unknown characters
aka characters that do not belong to the current set of labels within the
automaton. It is different from the a_delete, since it checks that the current
character from with the string DO not belong to the valid labels in the
automaton. This flag might be used with the a_split action do deal with no
letter characters.
a_track: the automaton returns in the final vector, a second map
that contains for each word the transformations that occurred.
5.
load (string
pathname,string codefeature): Load a text file, in which each line exposes one word.
The “codefeature” parameter is optional. It is used to detect in a string where
the feature section starts.
6.
loadlemma(string
pathname,string codefeature): Load a text file in which
lines work in pair. On the even line is the surface form and on the odd line is
the lemma+features form. The codefeature parameter is optional.
It is used with the a_full flag.
7.
loadcompact(string
pathname,string codefeature): Load a file in which the
automaton has been stored in a compact way (see store). The codefeature parameter
is optional. It is used with the a_full
flag.
8.
look(string
wrd): check if a word belongs to the
automaton in conjunction with the model function. This function returns a fmap
with the selected words as key and their scores.
9.
look(string
wrd,self o): check if a
word belongs to the automaton in conjunction with the model function. This
function returns a fmap with the selected words as key and their scores. The
difference with the above version is that it provides an object that replaces
the one provided with the definition of the automaton. This object will be the
last argument in the call to the associated function.
10.
model(function): defines the model function.
11.
setcodefeature(string
c): defines the code which is used to detect the
beginning of the feature structure. If it has been already defined with load,
then returns an error.
12.
settransformationmap(map
m): Set the transformation map to set the weights
when traversing the automaton with editdistance
13.
spotfinalstate(bool): Detect final states in the automaton and add either a
“$” to the current label if it is a string, or makes it negative otherwise. (see
below for model functions).
14.
store(string
c): stores a file into a compact mode, which both
smaller and faster to load than a text version of that file.
An
automaton can be declared with a function, which is used to control the
behavior of the automaton character by character.
Two types
of functions are available with the following signature:
a)
The
simplest one is the following:
function simple(int c,ivector
labels,float score)
where:
a)
c
is the current character (it is negative, when it is the first character in the
string) as unicode
b)
labels
are the next label Unicode codes. When a label is associated with a final
state in the automaton (a word boundary so to speak), then the code is negative
(<0).
c)
score
the current score.
b)
A
more complex function is:
function cpl(string mystr,string
base,int current,svector labels,svector actions,float score,self o)
where:
a)
mystr
is the current string being analyzed
b)
base
is the current substring extracted so far
c)
current
is the current character on which the automaton is positioned
d)
labels
is a vector of the next arcs presented as strings. When a label is associated with a
final state in the automaton (a word boundary so to speak), then a ‘$’ is
appended to the label.
e)
actions
is a vector of all the actions that were applied up to now. It does not contain
characters which did not undergo any modification.
f)
score
is the current score
g)
is
a user provided object (it can have any types)
The two functions must return a fmap or a vector object.
a)
The
fmap should have as keys the next
labels and as values their modified score. In the case of the first function,
the labels should be transformed into string first, with the function ord, before being stored in the fmap. When you build this fmap, you must use as keys the values
from the labels vector. However, if
you want the automaton to add or skip a character from the initial string, you
can use the “~” value in the key string. The value “~” alone means that the
character should be skipped, while “~” concatenated with a label such as “~o”
means that the character should be appended in the recognition process.
fmap
fm={‘a’:0.9,’b’:0.8}
b)
You
can also return a vector of vectors, where each sub-vector contains two
elements, the first is the label, the second one is the new score.
vector
v=[[‘a’,0.9],[‘b’:0.8]];
The main
difference between the two structures is that the first one does not provide
any specific order in which new arcs should be explored.
The process is
rather simple. The “look” function traverses the string character by character.
For instance, the string “zone” will be analyzed in the following way. The
first character is “z”. The system will then search in the automaton an initial
state which matches “z”. If such a state exists, then it will call one the
above functions, storing in labels all the “arcs” or the characters that can
follow a “z” in a dictionary. The user function will then mark for each of
these labels which one should be kept, together with a specific score. The
process is then recursively applied to each of these labels.
z – i – n – g
o – n – e
– a – l
We have stored for example, in the above
automaton the following three words: zing,
zone and zonal.
We want to find a correction for the
word: zong.
We start with a “z”, the first letter of
our word. In this case, in our automaton, we have only a “z” and the system
returns only for labels: labels=[‘z’].
We then go to the next character in our
string: ‘o’.
As we can see in our automaton, after a
“z” only the characters “i” and “o” are available.
The system will then call our function
with the following values for “labels”: labels=[‘o’,’i’].
We analyze these labels and we come up
with four potential actions:
a)The “o” is correct. It is both in the
automaton and is the current character in our string. We add this label to our
fmap: res[“o”]=score. We do not need to modify our score in this case, since it
is the same value.
b)The “o” is incorrect. It might be a “i”.
However, since the two characters are different, then there is a penalty to
choose a “i” instead of a “o”. We add to our fmap result: res[“i”]=score+1.
c) The “o” is wrong and should be deleted. We
add to our fmap result: res[“~”]=score+1, since there is a penalty to remove a
character from the initial string.
d)We could also add the ‘o’ and ‘i’ to the
current string, and continue from there on. We then add to our fmap result:
res[“~i”]=score+1 and res[“~o”]=score+1.
Our fmap result is then: res={“o”:0,
“i”:1, “~”:1, “~i”:1, “~o”:1}.
Our look up function will then use each
of these labels as a way to explore the rest of the automaton.
i. For instance, if it selects
“i” as the next character, then the score will be 1, and there will be a 0 path
after to recognize “ng”, (see the automaton.)
ii. If it selects “o”, then the
penalty will be 1 again as “e” and “g” will be different (“zone” and “zong”).
If it selects other paths, then the
penalty could be even higher.
To use one of these functions, you need to declare
them with the automaton itself as a “with”
function or with the model method.
automaton au(ch) with cpl;
The “in”
operator is only used to detect if a word belongs to the automaton or not. It
doesn’t work in conjunction with the model function.
Features
can be appended to the different strings either in the word file or in an “add”
function. These features are not excluded from the word detection but are still
added to the solutions.
automaton au;
au.setcodefeature("+");
au.add("zonking+Sg+Noun");//
+Sg+Noun will be isolated from ‘zonking’
The edit
distance function can be used with different actions that can be combined
together. Each of these actions can be pretty costly in terms of computational
efficiency.
automaton au;
au.load("lexicon");
//we combine character change with character deletion
and character insertion
//the system will not try these actions on the first
character…
au.editdistance("zoning",2,a_change|a_delete|a_insert);
//This function is the simplest one you
can use.
function simple(int c,ivector labels,float score) {
fmap res;
//If
the score is too large then we leave...
if (score>2)
return(res);
int i;
string a;
bool start=false;
//
when the current character is negative, then it means that it is the first
//
character from the string
if (c<0) {
start=true;
c*=-1;
}
//If
the first character belongs to the current labels, then we only keep
//this
character in the fmap.
if (start && c in labels)
res[c.chr()]=0;
else {
//we
want to put a score on each next arc...
//We
skip the current character
res["~"]=score+1;
for (i in labels) {
//Here we must transform this code into
a string
a=i.chr();
//We add it to the recognition string
res["~"+a]=score+1;
//if the current character is different
from one of the possible labels
if (i!=c)
//then we replace it
res[a]=score+1;
else
//we do nothing it is the same character
res[a]=score;
}
}
//we
then return the fmap which will be used by the automaton to process the next
character in the string.
return(res);
}
//In a certain way, these functions
compute an edit distance but based on an actual automaton to guide the process
function complex(string mystr,string base,
int current,svector labels, svector actions,
float
score,self o) {
fmap res;
//If
the score is too large we stop
if (score>2)
return(res);
string c=mystr[current];
string a;
//If
this is the first character in the string and c is in the labels
if (current==0 && c in labels)
res[c]=0;
else {
//we
want to put a score on each next arc...
//We
skip the current character
res["~"]=score+1;
for (a in labels) {
//in this case, a is a string, a label
from the automaton
//We add it to the recognition string
res["~"+a]=score+1;
if (a!=c)
//replacement of a character
res[a]=score+1;
else
res[a]=score;
}
}
//we
must return a fmap as a result
return(res);
}
//The word to check (it does exist)
string ch="zannking";
//we declare our automaton, here with
the first function
automaton au(ch) with simple;
//we load our word list file
au.load('C:\XIP\Test\kifcxx\phonologie\FST\data\englishlexiconmin.txt');
//We test if this word belongs to the
automaton
if ("zonking" in au)
println("Ok");
//we use the "simple" function
to find the corresponding words.
//the result is a fmap
println(au.look(ch));
This object is used to handle a tree structure. It provides
the following methods:
1. daughter(): return the first tree node as a tree object
2. daughter(tree n): Add n as the first daughter to the current node
3.
depth(): Return the depth of the node in the tree
4. editdistance(tree v)
: edit distance between two trees
5.
isolate(): Extract the
current node from its tree
6. lastnode(): return the last child tree node as a tree object
7.
lastnode(tree
n): Test if the current node is the last child of n
8. sister(tree n): add n as a sister node to the current node
9. sister(): return the next
tree node as a tree object
10. mother(): return the
parent tree node as a tree object
11. mother(tree n): Test if the current node is a parent of n
12. previous(): return the first tree node as a tree object
13.
previous(tree
n): add n as the previous node
14.
prune(): Delete the current sub-tree from the global tree
15.
tree n=100: modify the value of a tree node with anything, here with an integer, but
it could any object.
16.
tree(value): create a tree node, with value as a value. Value can have any types
x in tree:
return true or a list of tree nodes, according to the receiving variable.
for (s in tree) {…}: loop
among all keys.
Return the tree value as a string
Return the tree value as a integer or a
float
//Recursive traversing of a tree
function treedisplay(tree t) {
if (t==null)
return;
print(t," ");
if (t.daughter()!=null) {
print("("); //subnodes
are displayed between “(…)”
treedisplay(t.
daughter());
print(")");
}
treedisplay(t.sister());
}
//we create five nodes, with
numerical values
tree test1(1);
tree test2(2);
tree test3(3);
tree test4(4);
tree test5(5);
test1.daughter(test2);
test1.daughter(test3);
test2.daughter(test4);
test4.sister(test5);
treedisplay(test1); //we
display now: 1 (2 (4 5 )3 )
//We modify the value of test5
test5=[100,200];
treedisplay(test1); //we
display now: 1 (2 (4 [100,200] )3 )
//we remove test4
test4.prune();
treedisplay(test1); //we
display now: 1 (2 ([100,200] )3 )
//we use our values to add
int cpt=test1+test2+test3;
println(cpt); //we
display 6
self u; //As we do
not know anything about the values, we use a self
for (u in test1)
print(u,"[",u.depth(),"] ");//display:
1[0] 2[1] [100,200][2] 3[1]
These iterators are used to iterate on any objects of type: string, vector, map, rule.
riterator is the reverse iterator,
which is used to iterate from the end of the collection.
1. apply(a,b,c):
apply a function
2. begin():
initialiaze the iterator with the
beginning of the collection
3. end():
return true when the end of the
collection is reached
4. isvaluetype(string
type): test the type of the current element
5. key():
return the key of the current element
6. nend():
return true while the end of the
collection has not been reached (~end())
7. next():
next element in the collection
8. value():
return the value of the current element
9. valuetype():
return the value type of the current
element
10. setvalue(value): set value to the position pointed by the iterator.
An iterator is initialized through a
simple affectation.
vector v=[1,2,3,4,5];
iterator it=v;
for (it.begin();it.nend();it.next())
print(it.value(),",");
Run
1,2,3,4,5,
This type is used
to handle dates.
1. date():
return the date as a string
2. day():
return the day as an integer
3. format(string f): return the format as a string. The
format string uses a combination of options. See below for an explanation.
4. hour():
return the hour as an integer
5. min():
return the min as an integer
6. month():
return the month as an integer
7. sec():
return the sec as an integer
8. setdate(year,month,day,hour,min,sec):
set a time variable
9. year():
return the year as an integer
10. yearday():
return the year day as an integer between
0-365
11. weekday():
return the week day as an integer between
0-6. 0 is Sunday.
+,-: dates
can be added or subtracted
return the date as a string
return the number of seconds elapsed
since 00:00 hours, Jan 1, 1970 UTC
%a: The abbreviated weekday name
according to the current locale.
%A: The full weekday name according to the current locale.
%b: The abbreviated month name according to the current locale.
%B: The full month name according to the current locale.
%c: The preferred date and time representation for the current locale.
%C: The century number (year/100) as a 2-digit integer. (SU)
%d: The day of the month as a decimal number (range 01 to 31).
%D: Equivalent to %m/%d/%y.
(Yecch-for Americans only. Americans should note that in other countries %d/%m/%y is rather common. This means that in
international context this format is ambiguous and should not be used.) (SU)
%e: Like %d, the
day of the month as a decimal number, but a leading zero is replaced by a
space. (SU)
%E: Modifier: use alternative format, see below. (SU)
%F: Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
%G: The ISO 8601 week-based year (see NOTES) with century as a decimal
number. The 4-digit year corresponding to the ISO week number (see %V). This has the same format
and value as %Y, except
that if the ISO week number belongs to the previous or next year, that year is
used instead. (TZ)
%g: Like %G, but
without century, that is, with a 2-digit year (00-99). (TZ)
%h: Equivalent to %b.
(SU)
%H: The hour as a decimal number using a 24-hour clock (range 00 to
23).
%I: The hour as a decimal number using a 12-hour clock (range 01 to
12).
%j: The day of the year as a decimal number (range 001 to 366).
%k: The hour (24-hour clock) as a decimal number (range 0 to 23);
single digits are preceded by a blank. (See also %H.) (TZ)
%l: The hour (12-hour clock) as a decimal number (range 1 to 12);
single digits are preceded by a blank. (See also %I.) (TZ)
%m: The month as a decimal number (range 01 to 12).
%M: The minute as a decimal number (range 00 to 59).
%n: A newline character. (SU)
%O: Modifier: use alternative format, see below. (SU)
%p: Either "AM" or "PM" according to the given
time value, or the corresponding strings for the current locale. Noon is
treated as "PM" and midnight as "AM".
%P: Like %p but in lowercase: "am" or
"pm" or a corresponding string for the current locale. (GNU)
%r: The time in a.m. or p.m. notation. In the POSIX locale this is
equivalent to %I:%M:%S %p.
(SU)
%R: The time in 24-hour notation (%H:%M). (SU) For a version
including the seconds, see %T below.
%s: The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000
(UTC). (TZ)
%S: The second as a decimal number (range 00 to 60). (The range is up
to 60 to allow for occasional leap seconds.)
%t : A tab character. (SU)
%T: The time in 24-hour notation (%H:%M:%S). (SU)
%u: The day of the week as a decimal, range 1 to 7, Monday being 1.
See also %w. (SU)
%U: The week number of the current year as a decimal number, range 00
to 53, starting with the first Sunday as the first day of week 01. See also %V and %W.
%V: The ISO 8601 week number (see NOTES) of the current year as a
decimal number, range 01 to 53, where week 1 is the first week that has at least
4 days in the new year. See also %U and %W.
(SU)
%w: The day of the week as a decimal, range 0 to 6, Sunday being 0.
See also %u.
%W: The week number of the current year as a decimal number, range 00
to 53, starting with the first Monday as the first day of week 01.
%x: The preferred date representation for the current locale without
the time.
%X: The preferred time representation for the current locale without
the date.
%y: The year as a decimal number without a century (range 00 to 99).
%Y: The year as a decimal number including the century.
%z: The +hhmm or -hhmm numeric timezone (that is, the hour
and minute offset from UTC). (SU)
%Z: The timezone or name or abbreviation.
%+: The date and time in date format.
%%: A literal '%' character.
date d;
println(d.format("%Y%m%d")); display
date for 2015/12/25 as 20151225
date mytime;
print(mytime); // display: 2010/07/08 15:19:22
This type is
used to compute timeframes or duration.
1.
reset (): reinitialize
a time variable
+,-: time
can be added or subtracted
return the time in ms
return the time in ms
time mytime;
print(mytime);
This type is used to manage a file in input and output. The
type “wfile” is used to handle UTF16 (UCS-2 more precisely) files.
1. eof():
return true
when the end of file is reached
2. file f(string filename, string
moderead): open
a file according to moderead. The possible values for moderead are:
a. “a”: append
b. “r”: read
c. “w”:
write
d. “w+”: append
3. find(string s,bool caseinsensitive):
return all positions in the file of the
string s.
4. get():
read one character from the file
5. getsignature():
return whether the file contains a
signature
6. openappend(string
filename): open
a file in append mode
7. openread(string
filename): open a file in read mode
8. openwrite(string
filename): open a file in write mode
9. read():
read a line from a file
10. readln():
read a line from a file
11. seek(int
p): position the file cursor at p
12. setsignature(bool s):set the UTF8 or UTF16 signature
(accordingly)
13. tell():
return the
position of the file cursor
14. unget():
return one character to the stream
15. unget(nb):
return nb character to the stream
16. write(string
s1,string s2,…): write strings in the file
17. writelen(string
s1,string s2,…): write
strings in the file, separating each string with a space, and adding a carriage
return at the end of the line.
18. writebin(int
s1,int s2,…): write
bytes in the file
UTF-8 and UTF-16 files might have a
signature at the beginning, which consists of three octets to define a UTF-8
file or two octets in the case of a UTF-16 file.
·
If you use the type “file”, then in order to read the
signature out, you must set the signature beforehand. This type can only be
used to read UTF-8 or binary files.
·
In the case of “wfile”, the signature is automatically set when the signature is
found at the beginning of the file. You can only read UTF-16 (UCS-2) files with
this type.
x in file: if x is a string, then it receives a line from the
file, if it is a vector, it pushes the line on the top of it. If x is an
integer or a float, it gets only one character from the stream.
file f;
f.openread(path);
string s;
svector words;
string w;
for (s in f) {//Using
the in operator
s=s.trim();
words=s.split(" ");
for
(w in words)
print("word:",w,endl);
}
f.close();
KiF provides the variable stdin to
handle the standard input. This variable can be quite useful to handle data coming
from a piped file for instance.
string s;
int i=1;
for (s in stdin) {
println(i,s);
i++;
}
If you store these lines in a small file say: stdin.kif,
then the content of the piped strings will be displayed with for each line a
specific number:
echo “The lady is happy” |
KiF stdin.kif.
This object is used to store a function, which can then be executed.
The call is done using the variable name as a function.
function display(int
e) {
print("DISPLAY:",e,"\n");
e+=10;
return(e);
}
call myfunc;
myfunc=display;
int i=myfunc(100); // display:
DISPLAY:TEST
print("I=",i,"\n"); //display: I=110
This type is
used to handle XML documents. It can be used to create a new XML document or to
parse one. It is possible to associate a function with an xmldoc variable when
parsing a document to have access to each node on the fly.
1.
close(): Close the current XML document
and clean the memory from all XML values.
2.
create(string topnode): Create a new XML document, whose main node has topnode as name. If
topnode is a full XML structure then use it to create the document...
3.
load(string filename): load an XML file
4.
node(): Return the top node of the
document.
5.
onclosing(function f,myobject o): Function to call when a closing tag is found (see associate function
below)
6.
parse(string buffer): load an XML buffer
7.
save(string filename,string encoding): Save an XML document. If encoding is omitted, then encoding
is “utf-8”
8.
serialize(object): Serialize as an XML document any KiF object
9.
serializestring(object): Serialize as an XML document any KiF object and return the corresponding
string. The document is also cleaned in the process...
10. xmlstring():
return an XML document as a string.
11. xpath(string
myxpath): Evaluate an XPath and return a vector of xml nodes.
The associate function must have the
following signature:
function xmlnode(xml n,
object);
It must be declared in the following way:
xmldoc mydoc(obj) with
xmlnode;
The xml type exposes methods to handle XML
nodes.
This type is
implemented as a placeholder for the xmlNodePtr
type from the libxml2 library (see http://xmlsoft.org/), hence the new
method which is necessary to get a new object for the current variable.
12. child():
return the first child node under current node
13. child(xml):
Add an XML node as a child
14. content(): Return the content of a node
15. content(string
n): Change the content of a node.
16. delete():
delete the current internal node.
17. line():
return the line number of the current node
18. id():
return the id of the current node (only with call functions)
19. name():
return the XML node name
20. name(string
n): Change the XML node name
21. namespace():
Return the namespace of the current node as a vector.
22. new(string
n): Create a new internal node.
23. next():
return the next XML node
24. next(xml):
Add an XML node after the current node
25. parent():
return the parent node above current node
26. previous():
return the previous XML node
27. previous(xml):
Add an XML node before the current node
28. properties():
Return the properties of the XML node
29. properties(map
props): Properties are stored in map as attribute/value
30. root():
return the root node of the XML tree
31. xmlstring():
return the XML sub-tree as a string.
32. xmltype()
: return the type of the XML node.
Return the XML node name
function test(xml n, self nn) {
map
m=n.properties();
println(n.name(),m,n.content());
}
xmldoc doc with test;
doc.load("resxip.xml");
xml nd=doc.node();
println(nd);
while (nd!=null) {
println(nd.content(),nd.namespace());
nd=nd.child();
}
xmldoc nouveau;
nouveau.create("TESTAGE");
xml nd=nouveau.node();
xml n("toto");
nd.child(n);
n.new("titi");
n.content("Toto
is happy");
nd.child(n);
nouveau.save("mynewfile.xml");
The type kif is
used to load a specific KiF program dynamically.
3. clean(): this function closes a session and
clean it.
4. debugfunction(debfunction,object): Enable a debug mode in which
debfunction is called before each instruction (see below)
6. int firstinstruction=compile(string
code): this
function compiles a piece of KiF code and returns the first instruction of that
code. More than one compile can be called before running the code.
9. open(): this function opens a session and return a session handler.
10. run(int firstinstruction): this function runs a piece of KiF code
that has been compiled with compile from the first instruction in a given
session.
11. runasthread(int firstinstruction): this function runs a piece of KiF code
that has been compiled with compile from the first instruction in a given
session but as a thread.
12. runend(): this function returns true if the code
has been fully executed.
The functions available in the KiF file
can be called through a kif variable.
In our program test.kif, we implement the
function: Read
test.kif
function Read(string s) {
//we then
call a function in test.
_loader.End("From ‘call’ with love");
return(s+"_toto");
}
call.kif
In our calling program, we first load test.kif, then we execute Read
kif kf;
kf.load('c:\test.kif');
//we load a grammar implementing Read
string s=kf.Read("xxx");
//we can execute Read in our local program.
//we implement a local function,
which will be called from test through _loader…
function End(string s) {
println("We come back:",s);
}
Return the pathname of the KiF file
Return true if a KiF file has been
loaded.
If you need to access some variables or
some functions from a program that has already been loaded elsewhere, then KiF
guaranteed that as long as the path is the same, the other loadings will point
to the same version that was loaded in the first place.
So if prg1.kif
loads prg2.kif
and prg2.kif
loads prg3.kif,
which loads prg1.kif
again. The second prg1.kif will point to same memory space as the initial
loading of that program.
Another way to refer to another program
in memory is to add a method, which instantiates the current kif onto another one. We use this in this case to refer to the calling program. The only constraint is
that this method should implement a kif parameter.
prg1.kif
kif kf('c:\prg2.kif');
//we load a grammar, implementing a set method
kf.set(this);
//we set our local kif into prg2.kif.
prg2.kif
kif caller;
//This function will be called
from prg2 to instantiate caller with
//a reference to the loader. prg2
can then call functions implemented in //prg1.kif
function set(kif c) {
caller=c;
}
In this example, caller and _loader will
share the same value. Furthermore, in the case of a kif program loaded in a grammar, which has been loaded in another kif program, _loader will point to that initial kif program. Thus, it is possible to implement callback method in kif,
which will be called from a kif
program used in a grammar environment.
If you do not want external programs to
access specific functions, you can protect them by declaring these functions private.
//we implement a function, which
will cannot be called from outside
private function Cannotbecalled(string s) {…}
KiF also provides a specific function
to load into the current KiF space a KiF program. This operator can placed
anywhere in the code, and the variables and functions present in that program
are then available to the next instructions.
loadin
can take a second parameter, which is a Boolean expression,
whose execution decides on whether the file is loaded.
loadin('c:\files\prgm.kif');
//this program contains vccc, a vector
println(vccc); //which
is now available in the current program
loadin(_args[0],”tst” in _args[0]); //this program loads if its name contains tst
A session is a local compiling and
execution of some KiF code from strings. A session needs the following four
instructions:
The example
below reads a series of instructions and runs them. In this example, we compile
our code, line by line, but it is not mandatory. We could compile the whole
code in one single string.
kif session;
session.open(); //First
we open a session
int first=session.compile("int i=10;"); //we
compile the first line, which returns the position of the first instruction
session.compile("int j=10;"); //we add new
lines. We do not need any new first instruction of course
sessions.compile("println(i,j);"); //and a print
session.run(first);// we run from the first instruction provided by the first
_compile: it prints: 10 10
int second=session.compile("j=i+10;"); //we
can add new lines again... session.compile("println(i,j);");
//no need to run the previous lines
session.run(second);//we
run again, from a new instruction position
//it prints:
10 20
session.clean(); //end of the session
It is possible to have a specific
function being executed before any execution of any KiF instruction. debugfunction can be used at this
effect. The first parameter is a function whose signature is the following:
function
infodebug(string fname,string topname,int
ln,string label,self obj);
with:
·
fname: the current file name from which the code is executed
·
topname: the current function name which is executed
·
ln:
the current line in the file
·
label: if a label has been declared before the current line
·
obj: the data object that was passed a parameter to setdebugfunction
It is possible
to access the variables from within infos
with this method, which returns all the active variables, at the level of the
current instruction, within the debugging code.
The debugging
function (here infodebug) must return
either true or false. When it returns false
the current code execution is stopped.
This
method clears the debug function mode to revert to a normal execution
environment.
KiF provides all the necessary operations to handle all
sorts of algorithms: if, else, elif,
switch, for, while.
if (booleanexpression) {}
elif (booleanexpression) {}
…
else {}
The switch enables to list a
series of tests for one single object:
switch(expression) {
v1 : {…
}
v2 : {…
}
default:
{… //default is a predefined
keyword
}
}
v1,v2,..vn can be either a string or an integer or a
float. The expression is evaluated once and compared with v1, v2, vn…
It is also possible to replace the simple comparison
between the elements with a call to a function, which should return true or false.
//we test wether one value is larger
than the other
function tst(int
i,int j) {
if (j>=i)
return(true);
return(false);
}
int s=10;
//We test through test
switch (s) with tst {
1: println("1");
2: println("2");
20: println("20");
//This will be the selected occurrence
}
There are
different flavours of “for” in KiF. Here is a presentation of them all.
This for is composed of three parts, an initialisation, a
Boolean expression and a continuation part.
You can use continue or break to either go to the next element or to break in the middle
of a loop.
for (i=0;i<10;i+=1) print("I=",i,"\n");
Expressions,
both in the initialization part and in the increment part, can contain more than
one element. In that specific case, these elements should be separated by a
comma.
int i,j;
//Multiple initializations and
multiple increments.
for (i=10,j=100;i>5;i--,j++)
println(i,j);
This is a very specific sort of for, which is used to loop in a
container, a string or a file.
You can use the method “inkey” to get the current index value in
the loop. “inkey” uses as input, the
variable in which we are looping.
//we loop in a file
file f('myfile.txt',"r");
string s;
for (s in f)
println(s);
//we loop in a vector of ints...
vector v=[1,2,3,4,5,6];
int i;
for (i in v)
println(I,inkey(v));
//we also display the current position in the v while
looping…
This loop is equivalent to: for (i=start;i<end;i+=increment)…
Actually, the loop can also be
equivalent to: for (i=start;i>end;i+=increment)
if the increment is negative.
The reason for this loop is that it is
implemented as a C++ loop, and is about 30% to 50% faster than its equivalent.
Each of the values in the range can be instantiated through variables; however,
once the loop has started no element can be modified, including the variable
which receives the different values.
int
i,j=1;
int v;
time t1;
//Looping to 100000, with an increment of 1.
for (i in <0,100000,j>)
v=i;
time t2;
float
diff=t2-t1;
println("Elapsed time for fast ‘for’:",diff);
time t3;
for
(i=0;i<100000;i+=j)
v=i;
time t4;
diff=t4-t3;
println("Elapsed time for regular ‘for’",diff);
You can
also declare variables into a “for” statement, which are only to the “for”
code.
for (int
i in <0,100000,j>) println(i);
for (int
i=0;i<10;i++) println(i);
while is composed of a single Boolean expression.
while
(boolean) {…}
You can use continue or break to either go to the next element or to break in the middle of a loop.
int i=10;
while (i>0) {
print("I=",i,"\n");
i-=1;
}
This expression is similar to while, however, the first iteration is done before the Boolean
test.
int i=10;
do {
print("I=",i,"\n");
i-=1;
}
while (i>0);
This function can evaluate and run some
KiF code on the fly. The result of
the evaluation is returned according to what was evaluated.
These instructions are used to display results on the
current display port. The “err” versions display the results on the standard
error output. The “ln” version add two features to the output, for the values
separated with a “,”, an additional space is added. Second, a carriage return
is added at the end of the line.
These versions are quite different from the previous
one. The “j” stands for a join. These instructions are used to display
container values, which are “joined” beforehand. They accept either two or
three arguments. The first parameter should be a container and the second one a
separator string. If the container is a map, then a key separator can also be supplied.
If only the container is supplied, then the default
separator is the carriage return.
ivector v=[1..10];
printj(v,"-");
Result is: 1-2-3-4-5-6-7-8-9-10
map
m={1:2,2:3,4:5,6:7};
printjln(m,"-",",");
Result is: 1-2,2-3,4-5,6-7
These two
functions are used either to put a thread in pause or in sleep mode. pause does not suspend the execution of
a thread, while sleep does it.
pause takes as input a float, whose value is in seconds. Pause can take a second Boolean parameter to display a
small animation.
sleep is based on the OS sleep instruction
and its behavior depends on its local implementation. It takes as input an
integer.
pause(0.1); the thread will pause for 10 ms
pause(2,true); the thread will pause for 2s, with a small
animation
sleep(1); the thread will sleep for 1s (depending on
the platform)
KiF
provides a function to return a random value, which is between 0 and 99. random() returns a long value. You can also provide a maximum boundary value as an
argument.
float rd=random(); // value between 0 and 99
rd=random(999); //value between 0 and 999
KiF also provides a specific function getc(), which is used to return a keystroke. getc() returns the character code if the input variable is a int or a float or the character itself if the input variable is a string. To
transform a int into its encoding
character, use chr(). Below is an
example of a small program that reads a string as get().
int c;
string message;
while (message!=".")
{
print(">");
c=0;
message="";
while (c!=13 && c!=10) {
c=getc();
if (c!=13 && c!=10)
message+=c.chr();
print(c.chr());
}
println();
println("End:",message);
}
use
loads dynamic compatible library in a KiF program, to add new functionalities,
such as graphical interfaces, database management etc. The “OS” flag is
optional; it can take one of the following values:
“WINDOWS” ,
“MACOS”, “UNIX”, “UNIX64”.
This flag is used to load specific libraries according
to the platform architecture.
The library can
be a simple name, which must match a library name stored in the directory whose
path is recorded in the KIFLIBS environment variable. Library can also be a full path leading to this same library.
·
On
Unix platforms, library name are usually of the form: libmyname.so. To load such a library, you simple need to call: use(“myname”);
·
On
windows, library names are usually of the form: myname.dll. To load such a library, you simply need to call: use(“myname”).
It is usually
more generic to write: use(“myname”), so that the code will work on all
platforms without problems. However, you can use their full pathname, hence
limiting the use of this code to only specific platforms. The OS flag can then
be used to reinsert a little bit of generalization: use(“WINDOWS”,“kifsqlite”);
Try, catch and raise are used to handle
errors.
catch can be associated with a string or an
integer parameter. This variable is automatically set to null when the try
bloc is evaluated. A catch without variable is also possible.
string s;
try
{…
}
catch(s);
When an error is detected, then
the error string or its number is passed to that specific variable.
raise("201
My error");
This operator
is quite complex to handle, this is why we have a specific section dedicated to
it. In the previous description, we have already described some possible
utilization of that operator with files, vectors, maps or strings. We will now
see how it can be extended to encompass also frames.
A frame can expose an in function, which will then be used
when a in is applied to a frame. If a
in is tested against a frame object
without any in function, then a false value is always returned.
The operator in can be used with a comparison function. This function is
introduced with the with operator. It is called at each step in the object
recursive analysis. This function compares the value with each element of the
object and returns true or false accordingly.
This is a first example of the use of in with a map.
map dico;
vector lst;
dico={'a':1,'b':6,'c':4,'d':6};
//Boolean test, it returns true
or false
if (6 in dico)
print("As expected","\n");
//The receiver is a list, then we
return the list of indexes
lst=6 in dico;
string s;
for (s in lst)
print("LST:",s,"\n");
As expected
LST: b
LST: d
As
we can see on this example, the system returns some information in relation
with the type of receiver.
//In this function, i will always be instantiated with the value on the left of in.
function compare(int
i, int j) {
if (i<j)
return(true);
return(false);
}
if (3 in vect with compare)
print("OK");
lst=3 in vect with compare;
//In our example above, i=4…
frame testframe {
int i;
//the type of the parameter can be anything
function
in(int j) {
if (i==j)
return(true);
return(false);
}
}
This operator introduces some functional programming into
the language. The “on” operator is used to apply a function to a container. It
is quite similar to the apply method,
which is already associated with containers with a big difference, “on”
expressions can be embedded.
There are basically two “on” operators, which translate into
two different sorts of function.
These two “on” operators are not really different in form,
but quite different in their output. Again, as it often the case in KiF, the
selection of the “right” operator depends on the context.
The contexts in this case are either the recipient
variables, or the specific context in which the operator is called. We assume
in the following examples, that f0, f1are functions and c a container.
int v=
f0 on c; //in this case, we
want the execution of the "on" to be an integer value
v= 10+(f0 on c); //This case is a little different from the one above,
but the context again is an integer operation
ivector iv= f1 on c; //
This case is very different, we expect the output to be a container
v= f0 on (f1 on c); //
this case is the most complex. f1 on c0
is supposed to return a container and f0 on this output container will return a
value
As we can see on these examples above, the context requires
the “on” operator to return either a value or another container.
Basically, the rule is very simple, if the context expects a
value as in the two first examples, then the function should return a value.
If the context requires a container then the function should
return a container. If the context is underspecified, then the function will return
also a container as it is the case in f1
on c.
In all cases, the choice of the function relies on the user
decision.
Actually, the two functions, which will be used according to
the context, have almost the same signature.
The container function should be called in a container
context.
function fcontainer(iterator it);
function fcontainer(iterator it, self s); //to carry data throughout the process
function fcontainer(iterator it, self s=10); //with an initial value
The first parameter is always an iterator, which can be used
to get access to both the index and the value. Note that an iterator can also modify the local value with the method:
“set”.
The second parameter is optional. It should be a “self” type
if you want your function to carry its values through the whole process. If you
do not provide the initial value, then type of “s” will be similar to the first
element of your container.
Important: The
value returns by this function will be
stored in a container.
function fcontainer(iterator it);
function fcontainer(iterator it, self s); //to carry data throughout the process
function fcontainer(iterator it, self s=10); //with initial value
The value functions are very similar to the above
description. However, these functions should not return any values.
Important:
The “s” variable will be the one that
will be returned as the final result of your function, otherwise the function
returns true.
KiF also
accepts lambda functions with the
“on” operator. A lambda function is a
function which is declared “on site”, in lieu of a more global declaration.
There is little difference between a lambda
declaration and a function declaration, however the body of the function and
its name should be declared before the call to the “on” operator. A lambda function is declared with the keyword
lambda followed by the arguments and
the function body:
lambda(iterator it,self b=1) {your code…} on container;
NB: Lambdas can only be used with the “on” operator.
When these functions return “empty”, then the whole analysis
is stopped.
In this
example, we will simply return a container, in which the values from the
initial container have all been incremented with 1.
//Our initial container
imap iv={"a":1,"b":2,"c":4,"d":8,"e":16,"f":32};
imap
result;
//Our increment function, which returns the current
value + 1
function increment(iterator it) {
return(it.value()+1);
//Each value will store in our recipient container…
}
//Our call to increment on iv...
//Since result is an imap, each value returned by increment
//will be stored in the container
result=increment on iv;
The result is: {'a':2,'b':3,'c':5,'d':9,'e':17,'f':33}
Note
that we have kept the same keys…
//Our initial container
imap iv={"a":1,"b":2,"c":4,"d":8,"e":16,"f":32};
imap
result;
//Our multiply function, which multiply each value
with the previous ones
//The
initial value for “b” is 1...
//"b"
is kept alive across the whole process
function fmultiply(iterator it,self b=1) {
b*=it.value();
return(b);
}
//Our call to fmultiply on iv...
//Since result is an imap, each value returned by fmultiply
//will be stored in the container
result=fmultiply on iv;
The result is: {'a':1,'b':2,'c':8,'d':64,'e':1024,'f':32768}
//Our initial container
imap iv={"a":1,"b":2,"c":4,"d":8,"e":16,"f":32};
int result;
//Our result is an integer…
//Our multiply function, which multiply each value
with the previous ones
//The
initial value for “b” is 1...
//"b"
is kept alive across the whole process
function fmultiply(iterator it,self b=1) {
b*=it.value();
}
//Our call to fmultiply on iv...
result=fmultiply on iv;
The result is: 32768
//Our initial container
imap iv={"a":1,"b":2,"c":4,"d":8,"e":16,"f":32};
int result;
//Our result is an integer…
//Our multiply function, which multiply each value
with the previous ones
//The
initial value for “b” is 1...
//"b"
is kept alive across the whole process
function fmultiply(iterator it,self b=1) {
b*=it.value();
}
//Our increment function, which returns the current
value + 1
function increment(iterator it) {
return(it.value()+1);
//Each value will store in our recipient container…
}
//Our call to fmultiply on iv...
result=fmultiply on (increment on iv);
The result is: 151470
//An initial string
string s="12:1-14:2";
//We apply our lambda to the split on the
"-"
vector svr=lambda(iterator it) {return(it.value().split(":"));} on s.split("-");
The result is: [['12','1'],['14','2']]
KiF supplies
capabilities that are similar in a quite restrictive way to the Haskell
language.
The Haskell
Language is a functional language, which provides some very compact and
powerful ways to express specific mathematical problems, even though the
language is also usually presented as a general-purpose language.
We have added
to KiF some of the expressiveness power of the Haskell language with a specific
focus on a selected range of functions. We do not pretend that KiF behaves as a
full Haskell compiler, but it supplies some of the most interesting aspect of
this language.
In the rest of
this chapter, we will still use “Haskell” as a way to refer to the subset of
the language that was integrated into KiF, even though we are conscious that we
did not go very far into the very fabric of that language.
Before describing the language in more
details, we will present some specific operators, which have been introduced to
comply with some of the most interesting aspects of Haskell. These operators
are also available in KiF, but their interest really lies in the way they
enrich the Haskell world.
To comply
with the Haskell language, we have added a new way to declare a range of
elements: the “..” operator.
For instance
[1..10] defines the vector: [1,2,3,4,5,6,7,8,9,10].
By default the
step is 1, but it is possible to set a different step. You can either directly
define it with a “:” at the end of the expression:
For instance [1..10:2] defines the
vector: [1,3,5,7,9].
You can also
define this step by providing the next element in the definition:
For instance [1,3..10] defines the
vector: [1,3,5,7,9].
It also works
with characters:
For instance
[‘a’,’c’..’g’] defines the vector: ['a','c','e','g'].
The same vector
could also be defined with: [‘a’..’g’:2]...
Haskell also provides a notion of infinite range of
elements. There are two cases: you can either ignore the first element of the
set of the last element:
·
[1..] defines
an infinite vector that starts at 1, forward: [1,2,3,4…
·
[..1] defines an infinite vector that starts at 1,
backward: [1,0,-1,-2,-3…
You can also
use different steps:
·
[1..:2] defines an
infinite vector that starts at 1, forward: [1,3,5…
·
[..1:2] defines an
infinite vector that starts at 1, backward: [1,-1,-3…
Or
·
[1,3..] defines an
infinite vector that starts at 1, forward: [1,3,5…
·
[..-1,1] defines an
infinite vector that starts at 1, backward: [1,-1,-3…
These two
operators are used to concatenate a list of elements together or to add an
element to a vector.
This operator is used to merge different elements into
a vector. If one of the elements is not a list, it is simply merged into the
current list:
vector v=
7 &&& 8 &&& [1,2];
println(v);
v=[7,8,1,2]
This operator is similar to “++” in Haskell. Since
this operator was already defined in KiF, we modified it into
“&&&”.
This operator is similar to the other one, but with a
big difference, it merges the element into the current vector.
1::v ŕ [1,7,8,1,2] this is the new value of v
v::12 ŕ [1,7,8,1,2,12] this is the new value of v
All Haskell instructions in KiF should be declared
between “<..>”, which the internal KiF compiler utilizes to detect a Haskell
formula.
vector
v=<map (+1) [1..10]>;
The above
instruction adds 1 to each element of the vector.
The
simplest structure for a Haskell program is simply to return a value such as:
Ř <1>;
You can
return a calculus:
Ř <3+1>;
In that
case, the system will return one single atomic value.
Ř < 12+3> returns 15…
If you
need to use the binary operator “|”, then the expression should be in
parentheses, as this operator might be ambiguous with the “|” separator (see
below).
Ř < (12|3) > returns 15…
The Haskell language provides a very convenient and
efficient way to represent lists. In KiF, these lists will be implemented into
“vectors”, which could then be exchanged between the different structures.
The most basic Haskell instruction has the following
form:
Ř <x | x <- v, Boolean>
It returns a
list as result…
Which reads as:
1. We
add x to our current result list.
2.
We get x by iterating into v ó
x <- v
3.
We put a Boolean constraint,
which can be omitted.
The reason why it returns a list is due to the iteration in the expression.
<x | x <- [-5..5], x!=0> yields [-5,-4,-3,-2,-1,1,2,3,4,5]
You can combine different iterators together. There is
two ways to do it, either as if the two iterations were embedded one into the
other, or simultaneously.
The different iterators are separated with a “,”
Ř <x+y | x <-v, y <- vv, (x+y)>10>
The different iterators are combined with a “;”
Ř <x+y | x <-v ; y <- vv, (x+y)>10>
<x+y | x <-
[1..5], y <- [1..5]> //Combined
yields [2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,5,6,7,8,9,6,7,8,9,10]=25
elements…
< x+y | x <- [1..5] ; y <-
[1..5]> //simultaneous
yields
[2,4,6,8,10]=5 elements…
If you want to return an operation as value, as
exemplified in the above example, you must use parenthesis: (x+y)…
You can
also use vector patterns to extract element from the list, if the list is
composed of sub-lists.
vector
v=[[1,"P",true],[2,"C",false],[3,"E",true]];
vector
vv=<[y,t] | [y,n,t] <- v, y>1>;
yields: [[2,false],[3,true]]
KiF
already provides an iterator type,
which can be used in these Haskell expressions:
Ř <x.value()
| iterator x=v, x.value()!=0>
<x.key() | iterator x=[-10..20], x.value()%2==1>
yields:
[11,13,15,17,19,21,23,25,27,29]
KiF also
provides a specific way to iterate among maps, which in this case is quite
different from what is usually available in Haskell implementations.
KiF
already provides a mechanism to iterate among maps in “for”, with keys and values provided as a recipient to the iteration
process:
for ({x:y} in
m) ...
The same
mechanism is used here to iterate among values in a map, but also to return
specific values to the recipient map.
< {x:y}
| {y:x} <- m>;
//we
declare our map
map m={"a":1,"b":2,"c":3,"d":4};
//this
map is the recipient to the Haskell expression...
//We
iterator among key/value in m, and we return the same values inverted...
ismap mr=<
{x:y} | {y:x} <- m>;
Result is: {1:'a',4:'d',2:'b',3:'c'}
There are
different ways to declare local variables in a Haskell expression.
You can
use the “let” operator, which is used to associate a variable or a vector
pattern with an expression:
Ř <a | let a=10>;
Ř <a+b+c | let [a,b,c] =
[1,2,3]>;
“let” comes with two flavors. If the return
value has been declared with a “<x |…>” then different “let” expressions
can be separated one from the others with a “,”:
Ř <a+b | let a=10,let b=20>
However,
it is also possible to return a value through an “in” expression. In that case,
the different declarations will share one single “let”.
Ř <let a=10,b=20, in
(a+b)>
Note the
“,” before the “in” operator. This “,” does not exist in the official Haskell
language, but here since “in” is already a KiF operator, we have added this “,”
to disambiguate its interpretation.
If an
iteration is declared in your expression, then the “let” will be reevaluated at
each iteration.
Ř <a
| x <- [1..10], let a=x*2>
The
“where” operator is used to declare global variables. It is placed at the end
of a Haskell expression. Its evaluation is always done once before any other
analysis.
Ř <a
| let a=w+10, where
w=20;>
There
might be as many declarations in a “where” as necessary. Note that each
declaration should always end with “;”.
Ř <a
| let a=w1*w2, where
w1=20;w2=30;>
You can also declare functions in a where, which will
be local to that Haskell expression.
Ř <description(l)
: ("Liste="+<what l>) |
where <what([])
: "empty">;
<what([a])
: "one">;
<what(xs):
"large">;>;
description([]) yields
empty
description([1]) yields
one
description([1,2,3]) yields
large
The Haskell language also provides a way to declare
functions. These functions are equivalent to a KiF function in which all
arguments are of type “self”.
A
function is declared in the following way:
Ř <name(a1,a2…) : Haskell
expression>;
They can
be called from a KiF program with: name(p1,p2…).
<one(x) : (x+1)>;
int val=one(12);
val is: 15
<plusone(v) : (x+1) | x <- v>;
vector vect=plusone([1..10]);
vect is: [2,3,4,5,6,7,8,9,10,11]
In fact,
when you declare a Haskell function, KiF declares each non atomic element as a
“self” variable.
Hence
“plusone” declaration is equivalent to:
function plusone(self v) {…}
However, the arguments of these functions can be either atomic values
(integer, float or string) or vector declarations.
The
Haskell language provides a mechanism which is very similar to a switch/case:
the “guard”. A guard is a succession of tests associated with an action, each
test is introduced with a “|”. The default value is introduced with the keyword
“otherwise”.
<imb(bmi) : | bmi<=10 = "small" |
bmi<=20 = "medium" | otherwise = "large">;
imb(12)
yields “medium” as a response.
It is
actually possible to declare a function in more than one Haskell expressions.
In that case, the argument list can contain atomic values. When the expression
is evaluated, the parameters are tested against the arguments of the function.
If there is no match, then the system tries the next declaration.
<fibonacci(0) : 0>;
<fibonacci(1) : 1>;
<fibonacci(n) : a | let a=fibonacci(n-1)+fibonacci(n-2)>;
fibonacci(10) is 55
When “n”
is 0 or 1, it matches against the first or second definition, which then
returns the adequate value.
The “break”
can be used to “fail” the current function declaration. For instance, you might
want to go to the next declaration if the number of element is more than a
certain value.
<myloop(v): if (v.size()>10) break else v[0]>;
<myloop(v):
v[10]>;
<myloop([1..10])>
yields 1, the list size is 10
<myloop([1..20])>
yields 11, the list size is 20
This
instruction is very similar to a switch case, but with a big difference, it
compares x to patterns and not just values. For instance, you can provide
vector patterns in the list as a way and creates local variables.
Example:
//In this case, we test each value against 1,2 and we
return 12,24 or 34
vector
v=<case x of 1 -> 12, 2 -> 24 otherwise 34 | x
<- [1..10]>;
v is
[12,24,34,34,34,34,34,34,34,34]
//we prepare a vector in which we have:
[[1,2,3,4],..,[1,2,3,4]]
v=<replicate 5 [1..4]>;
v is
[[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
//we match the sub-lists against vector patterns
v=<case x of
[a,b] -> (a+b), [a,b,c,4] -> (a+b-c) otherwise <sum
x> | x <- v>;
v is
[0,0,0,0,0]
Haskell
can iterate on lists in a very similar way as Prolog. You can use the same
operator “|” as in Prolog or you can use the “:” operator as in Haskell to
define how the list should be split.
<see([ ]) : "empty">;
<see([first:rest]) : [a,first] | let a = see(rest)>;
see(['a'..'e']);
yields [[[[['empty','e'],'d'],'c'],'b'],'a']
Note also
the “;” at the end of each line…
You can
call any function or method, either Haskell or KiF in a Haskell expression. For
instance, let’s implement a simple “trim” on strings…
Ř <trim1(w)
: x | let x=w.trim()>;
//the simplest one
Ř <trim2(w)
: x | let x=<trim w>>; //pure
Haskell call
//We define a KiF function
function Trim(string c) {
return(c.trim());
}
Ř <trim3(w)
: x | let x=Trim(w)>;
//Through an external function
Ř <trim4(w)
: x | let x=<Trim w>>; //The
same, but with a Haskell flavor
Note that any method or function can be called from
within a Haskell expression as long as it matches the element type.
Ř <adding(v)
: <sum v>>;
There is no
actual difference between calling a function in a KiF way or using a Haskell
expression.
<fastsort([ ]) : [ ]>;
//if the list is empty, we return an
empty "list"
<fastsort([fv:v]) : (mn
&&& fv &&& mx) | //we merge the different sublists...
let mn
= fastsort(<a | a <- v, a<=fv>), //we apply our "sort" on the list that
contains the elements smaller than fv (First Value)
let mx = fastsort(<a | a <- v, a >fv>)>;//we apply our "sort" on the list that
contains the elements larger than fv
Haskell provides a set of specific functions, which
can only be used in Haskell expressions. These functions are used to apply functions
or methods to a list or to filter specific values out. Other functions are used
to duplicate or to cycle in a list.
This function gives
you the first n elements of the list. For instance, when you loop in an
infinite list, this function can be used to stop the iteration after a certain
number of elements have been extracted.
<take 10
[1,5..100]> yields [1,5,9,13,17,21,25,29,33,37]
This gives you everything
back except the first n elements of a list.
<drop 10
[1,5..100]> yields [41,45,49,53,57,61,65,69,73,77,81,85,89,93,97]
This
method is used to cycle in a list. It can be combined with a “take” in order to
limit the number of cycles.
v=<take 10 <cycle
[1,2,3]>> yields [1,2,3,1,2,3,1,2,3,1]
This
function creates a list, in which “value” is repeated ad infinitum. Again, you can combine it with a “take” to limit the
number of iterations.
v=<take 10 <repeat
5>> yields [5,5,5,5,5,5,5,5,5,5]
This
function duplicates “value” in a list of nb elements.
<replicate
3 [10]> yields [[10],[10],[10]]
You have
certainly noted that when we apply “take” on a list, which is created through
another function, we can control the number of elements that this inner
function generates: <take 10 <repeat 5>>. This operation is called “composition”.
It allows for a program to put a limit on what the sub-calls are doing. To
simplify the way these compositions are written, you can use the composition
operator: “.”
Hence, the formula <take 10 <repeat 5>> can also be written as:
<take 10 .repeat 5>
This
function is used to apply an operation or a function to each element of a list.
If “op” is reduced to an operator, then each element is combined with itself
with this operator. You can also use lambda expressions of the form (\x ->
…)
If a single
operator is supplied, then it is applied to each element together.
Ř <map
(+) [1..10]> yields [2,4,6,8,10,12,14,16,18,20]
(1+1/2+2/3+3/4+4/5+5/6+6/7+7/8+8/9+9/10+10)
In that case, we
provide both an operator and a value. Note that the position of the value in
the expression is important.
Ř <map
(-1) [1..10]> yields [0,1,2,3,4,5,6,7,8,9]
(1-1/2-1/3-1/…/10-1)
On the other hand:
Ř <map
(1-) [1..10]> yields [0,-1,-2,-3,-4,-5,-6,-7,-8,-9]
(1-1/1-2/1-3/1-4/…1-10)
A lambda in
Haskell is defined as: (\x0 x1..xn -> x0+x1+..+xn),
where x0 x1..xn are the arguments of the
lambda, followed by “->” and a specific calculus.
In the case of a
map, the lambda has only one argument.
Ř <map
(\x -> (x+4)/3) [1..10]> yields [1,2,2,2,3,3,3,4,4,4]
You can also
apply a function to each element of the list, with a map.
Ř <map
(cos) [0,0.1..0.4]> yields:
[1,0.995004,0.980067,0.955336,0.921061]
This function
can also be defined as a KiF function or as a Haskell function.
function Min(float y) {
return(y-1);
}
<map (Min) [1..10]>
yields [0,1,2,3,4,5,6,7,8,9]
filter is used to filter each element from a
list corresponding to a specific property. This property can be expressed with
a comparison operator, with a lambda or with a function that returns true or false.
1.
A very
simple example, we only keep values > 3:
Ř <filter
(>3) [1..10]> yields [4,5,6,7,8,9,10]
2.
In
the next example, we use a lambda expression, which is a bit richer than a
simple operator. In our example, we only keep the even values.
Ř <filter
(\x -> (x%2)==0) [1..10]> yields [2,4,6,8,10]
3.
We can
also use a function to do the comparison:
function odd(int x) {
if (x%2==0)
return(false);
return(true);
}
Ř <filter
(odd) [1..10]> yields [1,3,5,7,9]
4.
Finally,
we can also compose our expression with a “map”
Ř <filter
(odd) . map (*3) [1..10]>
yields [3,9,15,21,27]
This
instruction returns true if condition is verified for each element.
Ř <and
(odd) . [1,3..11]>
yields true
This
instruction returns true if condition is verified for at least one
element.
Ř <or
(odd) . [1,2..10]>
yields true
takeWhile put a condition on each element from the
list. When this condition is not met
then the iteration on the list stops. It works in a similar way as “take”, but
instead of counting the elements, it put a condition on them.
1.
Iterating
on an infinite list
Ř <takeWhile
(<100) [1,11..]> yields [1,11,21,31,41,51,61,71,81,91]
As we can
see on this example, the iteration has stopped when the value returned from the
list is above 100…
2.
Combining
with a map and a filter
In this
example, we extract all the squares below 500 that are odd.
Ř <filter
(odd) . takeWhile (<500)
. map (*) [1..]>
The result is: [1,9,25,49,81,121,169,225,289,361,441]
This function
drops all the elements until an element does not match the condition. It then
keeps the rest of the list.
<dropWhile (isdigit)
“12345ABCD123” > yields ABCD123
Combine
different lists together. Each element from l1,..,ln is stored into a list.
Ř <zip
[0..2] [0..2] [0..2]>
The result is: [[0,0,0],[1,1,1],[2,2,2]]
zipWith combines different list together thanks
to f. If f is a lambda, then it should have as many arguments as the number
of lists in the expression.
1.
Combining
three lists together with “+”
Ř <zipWith
(+) [0..10] [0..10] [0..10]>
The result is: [0,3,6,9,12,15,18,21,24,27,30]
2. With a lambda function
Ř <zipWith (\x y z -> x*y+z) [0..10] [0..10] [0..10]>
The result is: [0,2,6,12,20,30,42,56,72,90,110]
3. Composing with a takeWhile and infinite
lists
Ř <takeWhile
(<100) . zipWith (\x y z
-> x*y+z) [0..] [0..] [0..]>
The result is : [0,2,6,12,20,30,42,56,72,90]
These
operators apply a function, a lambda or an operation on a list, with “first” as
a seed. The lambda function should have two arguments. The difference between
“foldl” and “foldr” is the direction of the “fold”. “foldl” starts from the
beginning of the list, while foldr starts from the end of the list. foldl then
traverses the list in a forward manner, while foldr traverses the list
backward.
If you
use a lambda expression, then the element from the list should be the first one
for foldr and the second one for foldl. The other element is an accumulator,
whose final value will be returned as a result of the fold expression.
The
result of these functions is a value, not a list…
1.
Summing
elements from a list, with as a first value 100
Ř <foldl
(+) 100 [1..10]>
yields 155… (100+1+2+3…+10)
2.
Accumulating
values in lambda expression with foldl
Ř <foldl
(\ acc x -> acc+2*x) 10 [1..10]> yields 120
Note that the element from the list: “x” is the second
element of the lambda expression, while the accumulator is the first one.
3.
Accumulating
values in lambda expression with foldr
Ř <foldr
(\ x acc -> acc+2*x) 10 [1..10]> yields 120
Note that the element from the list: “x” is the first
element of the lambda expression.
These two functions are similar to foldl and foldr,
but they take as a seed the first element of the list.
1.
Summing
elements from a list, the first value is 1…
Ř <foldl1
(+) [1..10]>
yields 55… (1+2+3…+10)
2.
Accumulating
values in lambda expression with foldl1
Ř <foldl1
(\ acc x -> acc+2*x) [1..10]> yields 109
Note that the element from the list: “x” is the
second element of the lambda expression.
3.
Accumulating
values in lambda expression with foldr1
Ř <foldr1
(\ x acc -> acc+2*x) [1..10]> yields 110
Note that the element from the list: “x” is the first
element of the lambda expression.
These
functions are very similar to the “fold” function, except that they store in a
list the intermediary results.
1.
Summing
elements from a list, the first value is 1…
Ř <scanl1
(+) [1..10]>
yields [3,6,10,15,21,28,36,45,55]
2.
Accumulating
values in lambda expression with scanl1
Ř <scanl1
(\ acc x -> acc+2*x) [1..10]> yields [5,11,19,29,41,55,71,89,109]
Note that the element from the list: “x” is the
second element of the lambda expression.
3.
Accumulating
values in lambda expression with scanr1
Ř <scanr1
(\ x acc -> acc+2*x) [1..10]> yields [100,98,94,88,80,70,58,44,28]
Note that the element from the list: “x” is the first
element of the lambda expression.
string s=@"
55512 70.7107 1 0 1 0 1 1 0 0 0 ok - so what is she attempting to download ?
56836 70.7107 1 0 0 0 1 1 0 1 0 ok do you have any data indication in the notification bar ?
80803 70.7107 1 1 0 1 0 1 0 0 0 then it appears that this device has no sd card slot.
89103 70.7107 1 0 1 0 1 1 0 0 0 well for most. there is no national data roaming charges
7203 68.6406 0 0 1 0 1 3 1 1 0 as in if you slide down the status bar to see the notifications
50244 67.082 1 2 0 0 1 2 0 0 0 no it would dot work in the s3 as the s3 uses a mini sim card
23519 66.8153 1 0 0 1 1 2 0 0 0 hi i have a new htc one ppd and the keyboard no longer?
35862 66.8153 0 0 1 0 1 2 0 1 0 if there is an update available , the phone will populate
37519 66.8153 0 0 1 0 2 1 1 0 0 in problem state , i dont see any usb related message
42803 66.8153 1 0 0 0 1 2 0 1 0 it puts it where ? so you get the icon but no sound ?
82234 66.8153 0 0 0 0 1 2 1 1 0 there are notifications in the notification bar
93971 66.8153 0 0 1 0 1 2 0 1 0 when the customer gets a new message
2056 61.2372 1 0 0 0 1 1 0 0 0 all right . well , there is an sd card slot in the phone
2161 61.2372 0 0 0 1 1 1 0 0 0 all right then i would suggest calling in our swap
2607 61.2372 1 0 0 0 1 1 0 0 0 alright , in this case , i will have to refer the customer
3083 61.2372 0 0 0 0 1 1 0 1 0 alright looks like it 's actually just a setting in this phone
3749 61.2372 0 0 0 1 1 1 0 0 0 and , in general the samsung galaxy note 7 appears
3945 61.2372 0 0 0 0 1 1 0 1 0 and do you see a data symbol in the notification bar ?
4248 61.2372 0 0 0 0 1 1 0 1 0 and in the notification bar do you see a network connection ?
4284 61.2372 0 1 0 0 1 1 0 0 0 and is there a sim card in that pjhone to save the contacts ?
"@;
//The dot product implementation : ![]()
<dot(v1,v2) :
sum . zipWith
(*) v1 v2>;
//The norm implementation : ![]()
<norm(v1)
: sqrt . sum . map (*)
v1>;
//norm could also be implemented as: <norm(v1) : sqrt . sum . map (\ a -> a*a) v1>;
//The cosine implementation
<cosine(v1,v2)
: if (d==0) 0
else (n/d) | let n=<dot v1 v2>, let d=<norm v1>*<norm v2>>;
//We are going to split the above text into its vaue components. First we
are only interested in the
//column from 3 to 10…
//The first step is to split along carriage return (\n)
//Then we split each line along white characters:
//such as: ['55512','70.7107','1','0','1','0','1','1','0','0','0','ok','-','so'…]
//we then filter from the third column (first column is 0) to only keep
one digit string…
//we get rid of the last column.
vector v;
v=< u[:-1] | line
<- < <split . trim x> | x <- <split s "\n">, x.trim()!="">,
let u=filter (in ['0'..'9']) line[2:]>;
//uni contains only 1. It has the same size as one element from v.
ivector uni=<replicate <size v[0]>
1>;
ivector iv;
//We traverse our vector, converting each element into a vector of
integers…
//And we compute the cosine distance between uni and these elements.
for
(iv in v)
println(iv,cosine(uni,iv));
The “with” operator
has already been described before. It is also possible to associate a function
to a variable or a frame with this operator. This associate function will be automatically called whenever the value
of the variable changes.
Such a function requires two parameters, the first one being the value of the
variable before and the second one, the new value for this variable. This
association is done once for all when the variable is declared.
If the variable belongs to a frame, then it is possible to
implement a three parameters function, in which case, the first parameter is
the frame to which this variable belongs.
The “with” operator can be used with any types of
variables such as string, int, float or
containers. The signature of the
associate function depends of course on the type of variable, but also whether
it is in a frame or not.
For these
basic types, the associate function is called whenever the variable is
modified. The function must have two parameters of the same type as the
variable.
function wfonc(string s1,string s2) {
println(s1,s2);
}
//Note that the intialisation should take place after the function
declaration
string s with wfonc="jj";
s="jkjk";
For these
types, the function is called when a value is added to the container with a “=”
sign. In that case, the function will display the key and the value that were
either inserted or modified into the container.
function wcontainer(int v,string vv) {
println(v,vv);
}
vector vx with wcontainer;
vx[0]="jj";
map m with autre;
m[10]="kskdlsad";
In the
case of a variable declared into a frame, the function signature will be the
same as above, but the first parameter should be a frame.
function calllocal(testcallwith tx,int before,int after) {
println("LOCAL",tx,before,after);
}
//We declare a variable in the frame with an associate function
frame testcallwith {
//a
local associate function
int i with
calllocal=10;
}
//We implement a trigger function
function react(string key,string value) {
println("dico",key,value);
}
//which we associate with dico
map dico with react;
//whenever dico is modified, the
"react" function is called
dico["e"]=2;
//We first implement our anode
frame
frame anode {
int i;
function
string() {
return(i);
}
function
seti(int j) {
println("J=",i,j);
i+=j;
}
}
//First part of our connexe frame
frame connexe {
vector
v;
}
//a trigger function, the first
parameter is a connexe element
function transconnexe(connexe f,int before,int after)
{
iterator
it=f.v;
//we iterate on each element of our vector
for (it.begin();it.nend();it.next())
it.value().seti(before+after);
}
//Then the rest of our frame
frame connexe {
//i is associated with our new trigger function
int i with transconnexe;
function
_initial(int j) {
i=j;
}
function
addanode(int j) {
anode
xn;
xn.i=j+10;
v.push(xn);
}
}
//The transconnexe function will
be automatically called, each time i
is modified.
connexe c(10); //It
will be called here
c1.i=100; //and
here
KiF offers a simple way to put threads in a wait state. The
process is very simple to put in place. KiF provides different functions at
this effect:
10. waitonfalse(var): this function put a
thread in a wait state until the value of var is set to false. Var
must be declared as synchronous.
11. waitonjoined(): this function waits for threads launched
within the current thread to terminate. These threads must be declared with the
flag join.
thread compte(int i)
{
int j=10;
i+=1;
i+=1;
i+=1;
j+=i;
println("j=",j);
wait("Here");
println("I=",i,j);
}
thread comptebis(int
i) {
int j=10;
i+=1;
i+=1;
i+=1;
j+=i;
println("bis j=",j);
wait("Here");
println("bis I=",i,j);
}
compte(5);
comptebis(10);
println("We
come back");
cast("Here");
If we execute
the program above, KiF will display in the following order:
j= 18
bis j= 23
We come back
I= 8 18
bis I= 13 23
There are cases
when it is necessary to prevent certain threads to have access to the same lines
at the same time, for instance, to force two function calls to fully apply
before another thread to take control. When a lock is set in a given function, then the next lines of this
function are no longer accessible to other threads, until an unlock is called.
If we take the
following example:
//We implement our thread
thread launch(string n,int m) {
int i;
println(n);
//we display all our values
for (i=0;i<m;i++)
print(i," ");
println();
}
function principal() {
//we launch our thread
launch("Premier",2);
launch("Second",4);
}
If we run it,
we obtain a display which is quite random, as threads execute in an
undetermined order, only known to the kernel.
PremierSecond
00 1 1
2 3
This order can
be imposed with locks, which will prevent the kernel from executing the same
bunch of lines at the same time.
We must add locks into the code, to prevent the
system from meshing lines in a terrible output:
//We re-implement our thread with
a lock
thread launch(string n, int m) {
lock("launch"); //We
lock here, no one can pass anymore
int i;
println(n);
//we display all our values
for (i=0;i<m;i++)
print(i," ");
println();
unlock("launch"); //We
unlock with the same string, to allow passage.
}
Then, when we run this piece of code again, we will
have a complete different output, which is more on par with what we expect:
Premier
0 1
Second
0 1 2 3
This time the
lines will display according to their order in the code.
The lock
strings are global to the whole code, which means that a lock somewhere can be unlock
somewhere else. It also means that a lock
on a given string might block another part of the code that would use the same
string to lock its own lines. It is therefore recommended to use very specific strings
to differentiate one lock from
another.
The above example could have been rewritten
with exactly the same behavior by a protected
function.
//We re-implement our thread as a
protected function
protected thread launch(string n, int m) {
int i;
println(n);
//we display all our values
for (i=0;i<m;i++)
print(i," ");
println();
}
This function will yield exactly the
same output as the one above. Protected
threads implement a lock at the very
beginning of the execution and release it once the function is terminated.
However, the advantage of using lock
over a protected function is the
possibility to be much more precise on which lines should be protected.
If the above
functions are useful in a multi-threaded context, there are not enough in some
cases. KiF provides two other functions, which are used to synchronize threads
on variable values. These functions can only be associated with simple types
such as Boolean, integer, float or string.
The role of
these two functions is for a thread to wait for a specific variable to reach a false value. False is automatically returned when a numerical variable has the
value 0, when a string is empty or when a Boolean variable is set to false.
This first function must be associated
with a variable at the declaration level, through a with. The role of this function is to be triggered by any
modification of this variable in order to detect whether this variable returns false. Any variable, including
frame variables, can be associated with a synchronous
function.
This other function will put a thread in a wait state until
the variable var reaches the value false.
//First we declare a variable stopby as synchronous
//Important: its initial value must be different from 0
int stopby with synchronous=1;
//We implement our thread
thread
launch(int m) {
//we reset
stopby with the number of loops
stopby=m;
int i;
//we display
all our values
for
(i=0;i<m;i++) {
print(i,"
");
//we
decrement our stopby variable
stopby--;
}
}
function
principal() {
//we launch our
thread
launch(10);
//we wait for
stopby to reach 0...
waitonfalse(stopby);
println("End
");
}
principal();
The execution
will delay the display of “END” until every single i has been output on screen.
0 1 2
3 4 5 6 7 8 9 End
If we remove
the waitonfalse, the output will be
rather different:
End 0
1 2 3 4 5 6 7 8 9
As we can see
on this example, KiF will first display the message “End” before displaying any
other values.
The waitonfalse synchronizes principal and launch together.
The example
above could have been implemented with wait
and cast as below:
//We implement our thread
thread launch(int
m) {
int i;
//we display all our values
for (i=0;i<m;i++)
print(i," ");
cast("end");
}
function principal() {
//we launch our thread
launch(10);
wait("end");
println("End");
}
principal();
However, one
should remember that only one cast
can be performed at a time to release threads. With a synchronous variable, the waitonfalse
can be triggered by different threads, not just the one that would perform a cast.
When a thread
must wait for other threads to finish before carrying one, the simplest
solution is to declare each of these threads as join, and then uses the method: waitonjoined().
Different
threads can wait on a different set of joined threads at the same time.
//A first thread with a join
join thread jdisplay(string s) {
print(s+"\r");
}
//which is launched from this
thread also "join"
join thread launch(int
x) {
int i;
for (i=0;i<5000;i++) {
string s="Thread:"+x+"="+i;
jdisplay(s);
}
//we wait our local threads to finish
waitonjoined();
println("End:"+x);
}
//we launch two of them
launch(0);
launch(1);
//and we wait for them to
finish...
waitonjoined();
println("Termination");
KiF embeds an inference engine, which can be freely
mixed with regular KiF instructions.
This inference engine is very similar to Prolog but
with some particularities:
a)
Predicates
do not need to be declared beforehand, in order for KiF to distinguish
predicates from normal functions. However, if you need to use a predicate that
will be implemented later in the code, you need to declare it beforehand.
b)
You
do not need to declare inference variables, however, their names are very
different from traditional Prolog names: they must be preceded with a “?”.
c)
Each
inference clause finishes with a “.” and not a “;”
d)
Terms
can be declared beforehand (as term variables). However, if you do not want to
declare them, you must precede their name with a “?” as for inference
variables.
e)
Probabilities
might be attached to predicates, which are used to choose as a first path the
one with highest probabilities.
N.B. For an adequate description of
Prolog language, please consults the appropriate documentations.
KiF exposes
three specific types for inference objects:
This type
is used to declare predicates, which will be used in inference clauses.
This type
exposes the following methods:
1.
name():
return the predicate name
2.
size():
return the number of arguments
3.
_trace(bool):
activate or deactivate the trace for this
predicate, when it is the calling predicate.
This
function is used to set a specific mode to deal with weights. mode can take different values, that can
be combined together, with the | operator:
·
pred_none:
this is the standard node, where weights are not taken into account…
·
pred_weight:
this mode applies a Veterbi algorithm to choose among the highest weights
first. When a solution is found, it acts as a cut and returns the solution with
the highest probability.
·
pred_normalize:
this mode normalizes the weights so that the sum of all weights is 1.
·
pred_randomize:
this mode defines a random path among predicates. If used in conjunction with
pred_weight, then it stops at the first solution.
·
pred_weightstack:
this mode pushes on a stack the different weights encountered during the
resolution. This mode can be associated with two functions:
o
function
compute_final(fvector fv): This function computes a final weight which is
associated to the final predicate. This function is provided by the user. If
this function is not supplied, then the system computes a product of each
weight normalized with the size of the stack.
§ N.B. Important, this function
receives a fvector as input, which has
been built as in a stack along a predicate resolution path. If you modify some of these values, they will
be stored back into their original predicates, when the system is
back-tracking.
o
function
compute_inter(fvector fv): This function must return true or false. If false is returned then the function acts
as a fail.
predicate sentence;
term
s,np,vp,d,n,v;
function calc(fvector v) {
return(v.product()/v.size());
}
int decompte;
function inter(fvector v) {
decompte++;
if (decompte>10)
return(false);
return(true);
}
sentence._trace(true);
_predicatemode(pred_weightstack,calc,inter);
sentence(s(?NP,?VP)) --> noun_phrase(?NP), verb_phrase(?VP).
noun_phrase(np(?D,?N)) --> det(?D), noun(?N).
verb_phrase(vp(?V,?NP)) --> verb(?V), noun_phrase(?NP).
det<0.1>(d("the")) --> ["the"].
det<0.4>(d("a")) --> ["a"].
noun(n("bat")) --> ["bat"].
noun(n("cat")) --> ["cat"].
verb(v("eats")) --> ["eats"].
//we
generate all possible interpretations...
vector vr=sentence(?Y,[],?X);
println(vr);
This type
is used to declare terms, which will be used in inference clauses (see the NLP
example below)
·
KiF
also provides the traditional lists ŕ la
Prolog, which can be used with the “|” operator to handle list
decomposition (see the NLP example below for a demonstration of this operator).
o
Example
predicate alist;
//in this clause, C
is the rest of the list...
alist([?A,?B|?C],[?A,?B],?C) :- true.
v=alist([1,2,3,4,5],?X,?Y);
println(v); č [alist([1,2,3,4,5],[1,2],[3,4,5])]
·
KiF
also provides an associative map, which is implemented as a KiF map, but in
which the argument order is significant.
o
Example:
predicate assign,avalue;
avalue(1,1) :- true.
avalue (10,2) :- true.
avalue (100,3) :- true.
avalue ("fin",4) :- true.
assign({?X:?Y,?Z:?V}) :- avalue (?X,1), avalue (?Y,2), avalue (?Z,3), avalue (?V,4).
vector v=assign(?X);
println(v); č [assign({'100':'fin','1':10})]
As you can see on this example, both keys and values can depend on inference
variables. However, the order in which these associations key:value are declared is important.
Thus {?X:?Y,?Z:?V} is different from {?Z:?V,?X:?Y }.
This type
is used to handle predicates to explore their names and values. A predicatevar can be seen as a function,
whose parameters are accessible through their position in the argument list as
a vector.
This type
exposes the following methods:
1.
map(): return the predicate as a map: [‘name’:name,’0’:arg0,’1’:arg1…]
2.
name(): return the predicate name
3.
query(predicate|name,v1,v2,v3):
build and evaluate
a predicate on the fly.
4.
remove(): remove the predicate from memory
5.
remove(db): remove the predicate from the database db
6.
size(): return the number of arguments
7.
store(): store the predicate in memory
8.
store(db): store the predicate value into the database db.
9.
vector(): return the predicate as a vector: [name,arg0,arg1…]
It should
be noted that the method “predicate”, which exists both for a map and a vector,
transforms the content of a vector or a map back into a predicate as long as
their content mimics the predicate output of vector() and map().
vector
v=[‘female’,’mary’];
predicatevar fem;
fem=v.predicate(); //we transform our
vector into a predicate.
fem.store(); //we store it in the fact base.
v=fem.query(female,?X);
//We build a predicate query on the fly
v=fem.query(female,’mary’);
//We build a predicate query with a
string
A clause
is defined as follow:
predicate<weight>(arg1,arg2…,argn) :-
pred(arg…),pred(arg,…), etc. ;
The “<weight>” is optional. It is used
while analyzing to push some predicates up. When weights are provided, they
defined a deterministic search.
A fact
can be declared in a program, with the following instruction:
predicate(val,val)
:- true.
If you
replace “true” with “false”, then this instruction is used to
remove the fact from the fact base.
N.B. It is possible to replace the above
expression with:
predicate(val,val).
KiF also
accepts disjunctions in clauses, with the operator “;”, which can be used in
lieu of “,” between predicates.
predicate mere,pere;
mere("jeanne","marie").
mere("jeanne","rolande").
pere("bertrand","marie").
pere("bertrand","rolande").
predicate parent;
//We then declare our rule as a disjunction...
parent(?X,?Y) :-
mere(?X,?Y);pere(?X,?Y).
parent._trace(true);
vector v=parent(?X,?Y);
println(v);
Result:
r:0=parent(?X,?Y) --> parent(?X6,?Y7)
e:0=parent(?X8,?Y9) --> mere(?X8,?Y9)
k:1=mere('jeanne','marie').
success:2=parent('jeanne','marie')
k:1=mere('jeanne','rolande').
success:2=parent('jeanne','rolande')
[parent('jeanne','marie'),parent('jeanne','rolande')]
KiF also
provides a cut, which is expressed with
the traditional “!”. It also provides the keyword fail, which can be used to force the failure of a clause.
Kif also
provides some regular functions from the Prolog language such as:
This
function asserts (inserts) a predicate at the beginning of the knowledge base.
Note that this function can only be used
within a clause declaration.
This
function asserts (inserts) a predicate at the end of the knowledge base. Note
that this function can only be used
within a clause declaration.
This
function asserts (inserts) a predicate into a SQLite database. Db should be a sqlite object (see store)
This
function removes a predicate from the knowledge base. Note that this function can only be used within a clause
declaration.
This
function removes a predicate from the database. Db should be a sqlite object (see remove).
This
function removes all instances of predicate “pred” from the knowledge base. If retractall is used without any
parameters, then it cleans the whole knowledge base. Note that this function can only be used within a clause
declaration.
This
function is used to force a ponderation on a fact. If no weight is associated
to the predicate, then its value will be 1.
Example: ponder(parent<0.1>(“Tess”))
This
function when used without any parameters returns all predicates stored in
memory as a vector. If you provide the name of a predicate as a string, then it dumps as a vector all
the predicates with the specified name.
//Note that you need to declare "parent" if
you want to use it in an assert
predicate parent;
adding(?X,?Y) :-
asserta(parent(?X,?Y)).
adding("Pierre","Roland");
println(predicatedump(parent));
A predicate
can be declared with a callback function, whose signature is the following:
function OnSuccess(predicatevar p, string s) {
println(s,p);
return(true);
}
string s="Parent:";
predicate parent(s) with OnSuccess;
parent("John","Mary") :- true.
parent("John","Peter") :- true.
parent(?X,?Y);
This
function should be associated with the predicate that will be evaluated. Each
time the evaluation on parent is
successful then this function is called. The second argument in the function
corresponds to the parameter given to parent
in the declaration.
If the
function returns true, then inference
engine tries other solutions, otherwise it stops.
If we run our above example, we obtain:
Parent: parent('John','Mary')
Parent: parent('John','Peter')
Persistence
is done with the help of a Sqlite database, which should be declared as follow
(see sqlite type for more
information):
sqlite db;
db.open(‘mydb’);
To make a
fact persistent, you must declare it within the database, with the method: predicate.
db.predicate(name,arity);
The arity is the predicate number of arguments.
This function creates a table, whose name is “name_arity”, and with as many columns named [P0,P1..PN] as indicated
by arity.
Fact can
be made persistent with the following operators:
·
store(db):
stores a predicate in the sqlite database db.
·
get(db): queries the database for a given predicate
·
remove(db): removes a predicate from
the database.
·
assertdb(predicate,db): store
a predicate in the sqlite database db. Assertdb is much more versatile than
store(db), which requires that all information be known.
·
retractdb(predicate,db): removes
a predicate from the database.
female(‘mary’) :- store(db);
//we store this predicate in our database.
KiF also
accepts DCG rules (Definite Clause Grammar), with a few modifications with the
original definition. First, Prolog variables should denoted with ?V as in the
other rules. Third, atoms can only be declared as strings.
predicate sentence,noun_phrase,verb_phrase;
term
s,np,vp,d,n,v;
sentence(s(?NP,?VP)) --> noun_phrase(?NP), verb_phrase(?VP).
noun_phrase(np(?D,?N)) --> det(?D), noun(?N).
verb_phrase(vp(?V,?NP)) --> verb(?V), noun_phrase(?NP).
det(d("the")) --> ["the",?X], {?X is "big"}.
det(d("a")) --> ["a"].
noun(n("bat")) --> ["bat"].
noun(n("cat")) --> ["cat"].
verb(v("eats")) --> ["eats"].
//we generate all possible interpretations...
vector vr=sentence(?Y,[],?X);
println(vr);
Evaluations
are launched exactly in the same way as a function would. You can of course
provide as many inference variables as you want, but you can only launch one
predicate at a time, which imposes that your expression, should be first be
declared as a clause if you want it to include more than one predicate.
If the
recipient variable is a vector, then all possible analyses will be provided.
The evaluation tree will be fully traversed.
If the
recipient variable is anything else, then whenever a solution is found, the
evaluation is stopped.
If you
use common variables in predicates, such as strings, integers or any other
sorts of variables, you need to remember that these variables are used in
predicates as comparison values. An example might clarify a little bit what we
mean.
string s="test";
string sx="other";
predicate comp;
comp._trace(true);
comp(s,3)
:- println(s).
comp(sx,?X);
Execution:
r:0=comp(s,3) --> comp(other,?X172) --> Fail
This clause has failed, because s and sx have different
values.
string s="test"; //now they have the same values
string sx="test";
predicate comp;
comp._trace(true);
comp(s,3)
:- println(s).
comp(sx,?X);
Execution:
r:0=comp(s,3) --> comp(test,?X173)
e:0=comp(test,3) --> println(s)test
success:1=comp('test',3)
Be careful when you design your clauses, to use
external variables as comparison sources
and not as instantiation. If you want to pass a string value to your
predicate, then the placeholder for that string should be a predicate variable.
string sx="test";
predicate comp;
comp._trace(true);
comp(?s,3)
:- println(?s).
comp(sx,?X);
Execution:
r:0=comp(?s,3) --> comp(test,?X176)
e:0=comp('test',3) --> println(?s177:test)test
success:1=comp('test',3)
The following
program solves the Hanoi tower problem for you.
//we declare our predicate
predicate move;
//Note the variable names, which all start with a
"?"
move(1,?X,?Y,_)
:-
println('Move the top disk from ',?X,' to ',?Y).
move(?N,?X,?Y,?Z) :-
?N>1,
?M is ?N-1,
move(?M,?X,?Z,?Y),
move(1,?X,?Y,_),
move(?M,?Z,?Y,?X).
//The result will be assigned to res
predicatevar res;
res=move(3,"left","right","centre");
println(res);
If
you run this example, you obtain:
Move the top disk from
left to right
Move the top disk from
left to centre
Move the top disk from
right to centre
Move the top disk from
left to right
Move the top disk from
centre to left
Move the top disk from
centre to right
Move the top disk from
left to right
move(3,'left','right','centre')
With this
program, you can find the common female ancestor between different people
parent relationship.
//we declare all our predicates
predicate ancestor,parent,male,female,test;
//Then our clauses
ancestor(?X,?X) :- true.
ancestor(?X,?Z) :- parent(?X,?Y),ancestor(?Y,?Z).
//Our parent relations, which are stored in the fact
base
parent("george","sam") :- true.
parent("george","andy") :- true.
parent("andy","mary") :- true.
male("george") :- true.
male("sam") :- true.
male("andy") :- true.
female("mary") :- true.
test(?X,?Q) :- ancestor(?X,?Q), female(?Q).
test._trace(true);
//In this case, since the recipient variable is a
vector, we explore all possibilities.
vector v=test("george",?Z);
println(v);
Execution with a trace:
r:0=test(?X,?Q) --> test(george,?Z14)
e:0=test('george',?Q16) -->
ancestor('george',?Q16),female(?Q16)
r:1=ancestor(?X,?X) -->
ancestor('george',?Q16),female(?Q16)
e:1=ancestor('george','george') -->
female('george') --> Fail
r:1=ancestor(?X,?Z) -->
ancestor('george',?Q16),female(?Q16)
e:1=ancestor('george',?Z19) -->
parent('george',?Y20),ancestor(?Y20,?Z19),female(?Z19)
k:2=parent('george','sam') --> ancestor('sam',?Z19),female(?Z19)
r:3=ancestor(?X,?X)
--> ancestor('sam',?Z19),female(?Z19)
e:3=ancestor('sam','sam') --> female('sam') --> Fail
r:3=ancestor(?X,?Z) --> ancestor('sam',?Z19),female(?Z19)
e:3=ancestor('sam',?Z23) -->
parent('sam',?Y24),ancestor(?Y24,?Z23),female(?Z23)
k:2=parent('george','andy') -->
ancestor('andy',?Z19),female(?Z19)
r:3=ancestor(?X,?X) --> ancestor('andy',?Z19),female(?Z19)
e:3=ancestor('andy','andy') --> female('andy') --> Fail
r:3=ancestor(?X,?Z) --> ancestor('andy',?Z19),female(?Z19)
e:3=ancestor('andy',?Z27)
--> parent('andy',?Y28),ancestor(?Y28,?Z27),female(?Z27)
k:4=parent('andy','mary') --> ancestor('mary',?Z27),female(?Z27)
r:5=ancestor(?X,?X) --> ancestor('mary',?Z27),female(?Z27)
e:5=ancestor('mary','mary') --> female('mary')
success:6=test('george','mary')
r:5=ancestor(?X,?Z) --> ancestor('mary',?Z27),female(?Z27)
e:5=ancestor('mary',?Z31) -->
parent('mary',?Y32),ancestor(?Y32,?Z31),female(?Z31)
[test('george','mary')]
predicate ancestor,parent,female,test,truc;
sqlite db;
//Our database declaration
db.open('persitent.db');
//We declare our predicates
db.predicate("female",1);
db.predicate("parent",2);
//Transaction mode
db.begin();
//Storing our predicates
female("stephanie") :- store(db).
female("liliane") :- store(db).
female("jeanne") :- store(db).
female("mary") :- store(db).
female("francoise") :- store(db).
parent("george",10.3234) :- store(db).
parent("george",100) :- store(db).
parent("george","sam") :- store(db).
parent("george","andy") :- store(db).
parent("andy","mary") :- store(db).
db.commit();
//Now we tell the system where to look for the
predicate values
parent(?X,?Y) :- get(db).
female(?X) :- get(db).
//But here: no change...
ancestor(?X,?X) :- true.
ancestor(?X,?Z) :- parent(?X,?Y),ancestor(?Y,?Z).
test(?X,?Q) :- ancestor(?X,?Q), female(?Q).
vector v=test("george",?Z);
println(v);
db.close();
predicate ancestor,parent,female,test;
sqlite db;
//Our database declaration
db.open('persitent.db');
string err;
//We declare our predicates in the database, with
their arity...
try {
db.predicate("female",1);
db.predicate("parent",2);
}
catch(err)
{
println(err);
}
//We need these two instructions to store our values
in the database...
addingfemale(?X) :- assertdb(female(?X),db).
adding(?X,?Y) :- assertdb(parent(?X,?Y),db).
//Now we tell the system where to look for the
predicate values
//THESE ARE TWO important lines, since we give here
the link between the database and the predicates...
parent(?X,?Y) :- get(db).
female(?X) :- get(db).
//An ancestestor has a a parent, who is 'self a
parent...
ancestor(?X,?X) :- true.
ancestor(?X,?Z) :- parent(?X,?Y),ancestor(?Y,?Z).
//We are looking for a an ancestor, whose descendant
is a female
test(?X,?Q) :- ancestor(?X,?Q), female(?Q).
test._trace(true);
//We add the following siblings to our database...
//Note the “;” at the end of the line, to trigger the
evaluation of our predicates.
//Here, the main difference with the previous example
is obvious, since we can define whatever
//values we want on the fly.
db.begin();
addingfemale("stephanie");
addingfemale("liliane");
addingfemale("jeanne");
addingfemale("mary");
addingfemale("francoise");
adding("george","sam");
adding("george","andy");
adding("andy","mary");
db.commit();
//Looking for a female with a specific parent...
vector v=test("george",?Z);
println(v);
//Testing whether "mary" is a female.
println(female("mary"));
//Looking for all parents...
v=parent(?X,?Y);
println(v);
db.close();
This
example corresponds to the clauses that have been generated out of the previous
DCG grammar given as an example.
//We declare our predicates
predicate
sentence,noun_phrase,det,noun,verb_phrase,verb;
//We also declare our terms...
term
P,SN,SV,dét,nom,verbe;
sentence._trace(false);
sentence(?S1,?S3,P(?A,?B)) :- noun_phrase(?S1,?S2,?A), verb_phrase(?S2,?S3,?B).
noun_phrase(?S1,?S3,SN(?A,?B)) :- det(?S1,?S2,?A), noun(?S2,?S3,?B).
verb_phrase(?S1,?S3,SV(?A,?B)) :- verb(?S1,?S2,?A), noun_phrase(?S2,?S3,?B)
//Note the use of the “|” operator…
det(["the"|?X], ?X,dét("the")) :- true.
det(["a"|?X], ?X,dét("a")) :- true.
noun(["cat"|?X], ?X,nom("cat")) :- true.
noun(["dog"|?X], ?X,nom("dog")) :- true.
noun(["bat"|?X], ?X,nom("bat")) :- true.
verb(["eats"|?X], ?X,verbe("eats")) :- true.
vector v;
v=sentence(?X,[],?A);
println("All the sentences that can be
generated:",v);
//we analyze a sentence
v=sentence(["the", "dog", "eats", "a", "bat"],[],?A);
println("The analysis:",v);
Execution:
All the sentences that can be generated:
[sentence(['the','cat','eats','the','cat'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['the','cat','eats','the','dog'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['the','cat','eats','the','bat'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['the','cat','eats','a','cat'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['the','cat','eats','a','dog'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['the','cat','eats','a','bat'],[],P(SN(dét(the),nom(cat)),SV(verbe(eats),SN(dét(a),nom(bat))))),sentence(['the','dog','eats','the','cat'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['the','dog','eats','the','dog'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['the','dog','eats','the','bat'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['the','dog','eats','a','cat'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['the','dog','eats','a','dog'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['the','dog','eats','a','bat'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(a),nom(bat))))),sentence(['the','bat','eats','the','cat'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['the','bat','eats','the','dog'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['the','bat','eats','the','bat'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['the','bat','eats','a','cat'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['the','bat','eats','a','dog'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['the','bat','eats','a','bat'],[],P(SN(dét(the),nom(bat)),SV(verbe(eats),SN(dét(a),nom(bat))))),sentence(['a','cat','eats','the','cat'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['a','cat','eats','the','dog'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['a','cat','eats','the','bat'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['a','cat','eats','a','cat'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['a','cat','eats','a','dog'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['a','cat','eats','a','bat'],[],P(SN(dét(a),nom(cat)),SV(verbe(eats),SN(dét(a),nom(bat))))),sentence(['a','dog','eats','the','cat'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['a','dog','eats','the','dog'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['a','dog','eats','the','bat'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['a','dog','eats','a','cat'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['a','dog','eats','a','dog'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['a','dog','eats','a','bat'],[],P(SN(dét(a),nom(dog)),SV(verbe(eats),SN(dét(a),nom(bat))))),sentence(['a','bat','eats','the','cat'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(the),nom(cat))))),sentence(['a','bat','eats','the','dog'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(the),nom(dog))))),sentence(['a','bat','eats','the','bat'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(the),nom(bat))))),sentence(['a','bat','eats','a','cat'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(a),nom(cat))))),sentence(['a','bat','eats','a','dog'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(a),nom(dog))))),sentence(['a','bat','eats','a','bat'],[],P(SN(dét(a),nom(bat)),SV(verbe(eats),SN(dét(a),nom(bat)))))]
The analysis:
[sentence(['the','dog','eats','a','bat'],[],P(SN(dét(the),nom(dog)),SV(verbe(eats),SN(dét(a),nom(bat)))))]
The code below displays an animation in which disks
are moved from one column to another. It merges both graphics and predicates.
//we declare our predicate
predicate move;
//The initial configuration... All disks are on the
left column
map
columns={'left':[70,50,30],'centre':[],'right':[]};
//we draw a disk according to its position and its
column
function disk(window w,int x,int y,int sz,int position) {
int start=x+100-sz;
int
level=y-50*position;
w.rectanglefill(start,level,sz*2+20,30,FL_BLUE);
}
function displaying(window w,self o) {
w.drawcolor(FL_BLACK);
w.font(FL_HELVETICA,40);
w.drawtext("Left",180,200);
w.drawtext("Centre",460,200);
w.drawtext("Right",760,200);
w.rectanglefill(200,300,20,460,FL_BLACK);
w.rectanglefill(100,740,220,20,FL_BLACK);
w.rectanglefill(500,300,20,460,FL_BLACK);
w.rectanglefill(400,740,220,20,FL_BLACK);
w.rectanglefill(800,300,20,460,FL_BLACK);
w.rectanglefill(700,740,220,20,FL_BLACK);
//Now we draw our disks
vector
left=columns['left'];
vector
centre=columns['centre'];
vector
right=columns['right'];
int i;
for
(i=0;i<left;i++)
disk(w,100,740,left[i],i+1);
for
(i=0;i<centre;i++)
disk(w,400,740,centre[i],i+1);
for
(i=0;i<right;i++)
disk(w,700,740,right[i],i+1);
}
window w with
displaying;
//------ Inference engine part -------------
//we move from column x to y
function moving(string x,string y) {
columns[y].push(columns[x][-1]);
columns[x].pop();
w.redraw();
//a little pause after redrawing the whole stuff
pause(0.5);
return(true); //Important, we return true… or the predicate fails.
}
//Note the variable names, which all start with a
"?"
move(1,?X,?Y,_)
:- moving(?X,?Y).
move(?N,?X,?Y,?Z) :-
?N>1,
?M is ?N-1,
move(?M,?X,?Z,?Y),
move(1,?X,?Y,_),
move(?M,?Z,?Y,?X).
//The inference is launched within a thread...
thread hanoi() {
move(3,"left","right","centre");
}
//--------------------------------------------------------------
function launch(button b,self o) {
hanoi();
}
//We put a button to lauch the inference engine
button b with
launch;
w.begin(50,50,1000,800,"HANOI");
b.create(20,20,60,30,FL_Regular,FL_NORMAL_BUTTON ,"Launch");
w.end();
w.run();
KiF provides a library, which offers
some system functionalities such as, reading the content of a directory into a
vector or executing a system command.
To
use this library: use('kifsys');
kifsys
also exports the type: sys together
with a sys variable: kifsys, which can be used to execute
system commands.
use('kifsys');
//This function copies all the
files from a given directory to another, if they are more recent than a given
date
function cp(string thepath,string topath) {
//We read
the content of the source directory
vector
v=kifsys.listdirectory(thepath);
iterator
it;
string
path;
string
cmd;
map
m;
date
t;
//we set today's date starting at 9A.M.
t.setdate(t.year(),t.month(),t.day(),9,0,0);
it=v;
for (it.begin();it.nend();it.next()) {
path=thepath+'\'+it.value();
//if the file if of the right type
if (".cxx"
in path || ".h" in path || ".c"
in path) {
m=kifsys.fileinfo(path);
//if the date is more recent than our current date
if (m["date"]>t)
{
//we copy it
cmd="copy "+path+'
'+topath;
println(cmd);
//We execute our command
kifsys.command(cmd);
}
}
}
}
//We call this function to copy
from one directory to another
cp('C:\src','W:\src');
kifsocket is a specific library which exports the type socket to handle socket interactions
between a client and a server.
To
use this library: use('kifsocket');
1.
connect(string
hostname,int port): connect
a client to a specific host on a specific port.
2.
createserver(int
port,int nbclients): create
a server on the local host with a specific port.
3.
createserver(string
hostname,int port,int nbclients): create
a server on a host with a specific port.
4.
gethostname():
return the current host name. The
socket does not need to be activated to get this information.
5.
settimeout(int
i): set the timeout in
seconds for both writing and reading on the socket. Use this instruction to
avoid blocking on a read or on a write. A value of -1
cancels the timeout.
6.
wait():
the server wait for a client to
connect. It returns the client identifier, which will be used to communicate
with the client.
Server Side
7.
close(): close the socket
8.
close(clientid): close the communication with clientid.
9.
read(clientid):
read a KiF object on the socket with
clientid
10.
receive(int
clientid):
read a simple string on the socket with clientid
11.
run(int
client,string stopstring): put
the server in run mode. Server can now accept Remote Method Invocation (RMI)
mode.
12.
send(int
clientid,string s): write
a simple string on the socket with clientid
13.
write(clientid,o1,o2...):
write KiF objects on the socket with
clientid.
Client Side
14.
close(): close the socket
15.
getframe(string
name): return a frame
object remote handle of name name.
16.
getfunction(string
name): return a function remote handle of name name.
17.
read():
read a KiF object on the socket
18.
receive(): read a simple string on the socket
19.
send(string
s): write a simple
string on the socket
20.
write(o1,o2...):
write KiF objects on the socket
//Server side
int clientid;
socket s; //we
create a socket
string name=s.gethostname();
println("Local
server:",name);
//We create our server on the
socket 2020, with at most 5 connections…
s.createserver(2020,5);
//we wait for a client connection
while (true) {
//we can
accept up to 5 connections…
clientid=s.wait();
//we read a
message from the client, it should be done in a //thread to handle more
connections.
string message=s.read(clientid);
message+=" and returned";
//we write a
message to the client
s.write(clientid);
//we close
the connection
s.close(clientid);
}
//We kill the server
s.close();
//Client side
socket s; //we
create a socket
string name=s.gethostname();
println("Local
server:",name);
//We create our server on the
socket 2020
s.connect(name,2020);
//we write a message to the
server
string message="Hello";
s.write(message);
//we read a message from the
server
message=s.read();
println(message);
//we close the connection
s.close();
The example, which is described below,
shows how a XIP server can be put in place in KiF. This server receives
sentences from the clients, which are parsed. The result of the parse is
returned as a string.
//We load the socket library
use('kifsocket');
parser french;
//we load a grammar
french.load('C:\FRENCH\french_eerqi.grm');
//This function will be used to
launch the parse of a sentence.
function parsing(string sx) {
string
res=french.parse(sx);
return(res);
}
//We read on a socket the
sentence from the client...
thread parse(int
num,socket s) {
string
sx;
string
res;
string
mes;
while (true) {
try {
//we read our sentence
sx=s.read(num);
if (sx!="")
{
//the we parse it
res=parsing(sx);
//we return the result to the client
s.write(num,res);
}
}
catch(mes) {
//in case of
error, we terminate the thread…
s.close(num);
return;
}
}
}
socket s;
//We create our server on the
port 2021, for 20 potential clients
s.createserver(2021,20);
println("Port:
2021");
int n;
while (true) {
//we wait for a client to connect
n=s.wait();
//we use this socket id to parse the sentence...
parse(n,s);
}
use('kifsocket');
socket s;
//we connect to our server, the
first argument is the port
s.connect(s.gethostname(),_args[0]);
//We set a timeout of 5s, in
order to avoid infinite socket reading...
s.settimeout(5);
file f;
//We open our file, which is the
second argument of our program
f.openread(_args[1]);
string ph;
string res;
int compte=0;
function parse(string p) {
int e;
//We display the chunk number
printlnerr("Example:",compte);
compte+=1;
try {
//we send our sentences to the
server
s.write(p);
//which returns the result as a string
res=s.read();
}
catch(e) {
//If a error occurs
println("Erreur:",e);
if (e==830) {
//830 is a TIME OUT error
printlnerr("Time out");
//we close the current socket
s.close();
//we reconnect to the server
s.connect(s.gethostname(),_args[0]);
//we try to write our sentence once again
s.write(p);
//and to read the result
res=s.read();
}
}
println(res);
}
//we read each string from the
file
for (ph in f) {
ph=ph.trim();
if (ph!="")
parse(ph);
}
kifsocket offers a second type: remote, which is used to run
methods and functions on a remote server. The server must launch a run on the server side to handle
execution requests from the client.
The client on the other hand, must request
frames and functions through two socket methods: getframe and getfunction
in order to launch these executions.
By default, any
objects and any functions can be requested by the client, which limits the
necessity to specific declarations on the server side.
These two
methods return a remote object, which
can be used to run any functions or methods.
However, if you
want to hide within your server, frames and functions, you must declare them as
private, which will prevent any
attempts from the client to reach specific functions or frame variables.
When a remote object is handled as a vector, it
returns the list of available functions on the server. In the case of a getfunction, the list will be limited to
one single element. However, in the case of a getframe, all available functions (non private) will be stored.
The string parameter for the run function is used to allow the client
to shut down the server, by sending a simple write with that specific string. To prevent a client from shutting down
a server, one possibility is to launch a run
with the empty string as a parameter.
The code below implements a frame and a
function. This server implements a function: create, which creates a new test
object as a global variable (via an object broker), and a frame object toto, which is accessible from the
client.
The server can be stopped with a
specific string, here: stop.
//A simple server of objects...
//First we declare a simple frame
frame test {
int i;
//This function should not be called on the client side
private
function _initial(int x) {
i=x;
}
//This function cannot be called on the client side
private
function Set(int j) {
i=j;
}
function
Value() {
return(i);
}
function
Compute(int j) {
return(i*j);
}
}
//The method creates a new frame test
object, with the name: n
function create(string name,int i) {
test
t(i);
_KIFMAIN[name]=t;
}
//We have our specific frame
object, which any client can ask for
test toto(100);
//This object is declared as “private”
and cannot be requested by the client…
private test mineDoNotTouch(-1);
//We create our server
socket s;
println(s.gethostname());
//On port: 2012
s.createserver(2012,10);
//we wait for a client to connect
int client=s.wait();
//The stop string is here, well:
"stop"
//We wait for the client to
execute functions on the server side...
s.run(client,"stop");
//If our server receives the
"stop" string, it stops.
s.close();
println("Stopped");
The client side implements a call to
the toto frame object, with an
execution of each of the methods exposed by this frame. It also calls create to create a new object on the
server side, which is in its turn executed on the server side. Finally, it
writes the stop string, which kills
the server.
//We connect to our local server.
socket s;
string name=s.gethostname();
s.connect(name,2012);
//The we declare a remote object:
r
remote r;
//We get from the server a handle
on toto, which is implemented on the server side
r=s.getframe("toto");
//We display the available
methods from the frame description of toto, which have been returned by the
server.
println("Functions:",r);
println("Values
of toto:",r.Value(),r.Compute(345));
//We then fetch a function:
create
r=s.getfunction("create");
//Which we can use to execute the
object created on the server side...
r.create("titi",123);
//we can now fetch a titi object,
that was created thanks to the above instruction
r=s.getframe("titi");
//And we display its values
println("Valeur
de titi:",r.Value(),r.Compute(345));
//We kill the server, by sending
the killing string.
s.write("stop");
s.close();
KiF
also provides a simple library to handle a SQLite database. SQlite is a very
popular database system which uses simple files to handle SQL commands. If you
want more information on SQLite, you will find plenty of it on the web. kifsqlite exports the type: sqlite
To
use this library: use('kifsqlite');
//we declare a new sqlite
variable
sqlite mydb;
//we open a database. If it does
not exist, it creates it...
mydb.open('test.db');
try {
//we insert a new table in the current database
mydb.create("table1","nom TEXT PRIMARY KEY","age INTEGER");
println("table1
est cree");
}
catch() {
//This database already exists
println("Deja cree");
}
int i;
string nm;
//We insert values in the
database, using a commit mode (which is much faster)
mydb.begin();
//We insert 5000 elements
for(i=0;i<5000;i+=1) {
nm="tiia_"+i;
try {
//we insert in table1 two values, one for 'nom' the other
for 'age'.
//Notice the alternation between column names and values
mydb.insert("table1","nom",nm,"age",i);
println(i);
}
catch() {
println("Deja inseree");
}
}
//we then commit our commands.
mydb.commit();
//we iterate among our values for
a given SQL command
iterator it=mydb.run("select
* from table1 where age>10;");
for (it.begin();it.nend();it.next())
println("Value: ",it.value());
//We could have obtained the same
result with:
//vector
v=mydb.execute("select * from table1 where age>10;");
//However the risk to overflow
our vector is pretty dangerous.
mydb.close();
LMDB is another
database manager, but of the form key/value. LMDB has been developed by Symas
for the OpenLDAP project (see http://symas.com/mdb/
for more information).
We have mapped
the LMDB manager onto KiF maps, but
with some specific enhancement over these maps.
1.
open(string
pathname,int size,int nbdbs,int flags,int fixedsize): open a database. See below for an
explanation of the flags. If “fixedsize” is 0, then records can have different sizes. If you set
“fixedsize” with a specific number, then keys and values will have this
“fixed” size to store their specific values, making the whole database access
faster, but less versatile.
2.
begin():
start
the transaction mode
3.
create(string
table,int flags): create
a table
4.
commit():
commit
transactions
5.
selfcommit(bool):
commit
transaction is automatically done when using 'var[key]=value'
6.
put(string
key,string value,int flags): store
a value
7.
delete(string
key):
delete a key
8.
cursormode(int
cursorflag): set
the cursor mode access
9.
get(string
key,int cursorflag): Get
a value with a key, with the size sz along a cursorflag... Use an iterator to
get values, without a call to 'begin'
10. close(): close a session
11. select(string table): switch to another table
1.
MDB_NODUPDATA - enter the new key/data pair only if it does not already appear in the database. This flag may only be specified if the database was opened with MDB_DUPSORT.
The function will return MDB_KEYEXIST if the key/data pair already appears in
the database.
2.
MDB_NOOVERWRITE - enter the new key/data pair only if the key does not already appear in the database. The function will
return MDB_KEYEXIST if the key already appears in the database, even if the
database supports duplicates (#MDB_DUPSORT). The data parameter will be set to
point to the existing item.
3.
MDB_RESERVE - reserve space for data of the given size, but don't copy the given
data. Instead, return a pointer to the reserved space, which the caller can
fill in later. This saves an extra memcpy if the data is being generated later.
4.
MDB_APPEND - append the given key/data pair to
the end of the database. No key comparisons are performed. This option allows
fast bulk loading when keys are already known to be in the correct order.
Loading unsorted keys with this flag will cause data corruption.
5.
MDB_APPENDDUP - as above, but for sorted dup data.
Here is
the list of cursor mode flags (also the flags that are used in ‘get’). Each
corresponds to a KiF constant variable.
1. MDB_CURRENT - overwrite the data of the key/data pair
to which the cursor refers with the specified data item. The key parameter is
ignored (to be used with get)
2. MDB_FIRST : Position at first key/data item
3. MDB_FIRST_DUP: Position at first data item of current key
(Only for MDB_DUPSORT)
4. MDB_GET_BOTH: Position at key/data pair (Only for
MDB_DUPSORT)
5. MDB_GET_BOTH_RANGE: position
at key, nearest data (Only for MDB_DUPSORT)
6. MDB_GET_CURRENT: Return key/data at current cursor position
7. MDB_GET_MULTIPLE: Return all the duplicate data items at the
current cursor position (Only for MDB_DUPFIXED)
8. MDB_LAST:
Position at last key/data item
9. MDB_LAST_DUP:
Position at last data item of current key (Only for MDB_DUPSORT)
10. MDB_NEXT : Position at next data item
11. MDB_NEXT_DUP: Position at next data item of current key (Only
for MDB_DUPSORT)
12. MDB_NEXT_MULTIPLE:
Return all duplicate data items at the
next cursor position (Only for MDB_DUPFIXED)
13. MDB_NEXT_NODUP: Position at first data item of next key. (Only
for MDB_DUPSORT)
14. MDB_PREV: Position at previous data item
15. MDB_PREV_DUP: Position at previous data item of current key
(Only for MDB_DUPSORT)
16. MDB_PREV_NODUP: Position at last data item of previous key
(Only for MDB_DUPSORT)
17. MDB_SET:
Position at specified key
18. MDB_SET_KEY: Position at specified key, return key +
data
19. MDB_SET_RANGE: Position at first key greater than or equal to
specified key.
LMDB returns the following error values:
1.
MDB_KEYEXIST: key/data pair already exists
2.
MDB_NOTFOUND: key/data pair not found (EOF)
3.
MDB_PAGE_NOTFOUND: Requested page not found -
this usually indicates corruption
4.
MDB_CORRUPTED: Located page was wrong type
5.
MDB_PANIC: Update of meta page failed, probably
I/O error
6.
MDB_VERSION_MISMATCH: Environment version
mismatch
7.
MDB_INVALID: File is not a valid MDB file
8.
MDB_MAP_FULL: Environment mapsize reached
9.
MDB_DBS_FULL: Environment maxdbs reached
10.
MDB_READERS_FULL: Environment maxreaders
reached
11.
MDB_TLS_FULL: Too many TLS keys in use -
Windows only
12.
MDB_TXN_FULL: Txn has too many dirty pages
13.
MDB_CURSOR_FULL: Cursor stack too deep -
internal error
14.
MDB_PAGE_FULL: Page has not enough space -
internal error
15.
MDB_MAP_RESIZED: Database contents grew beyond
environment mapsize
16.
MDB_INCOMPATIBLE: Database flags changed or
would change
It is possible
to use iterators with lmdb variables.
Actually, iterators can even be triggered with a get and continued with a next. See the example below to see how
to start this sort of iteration.
//We need to load it first (it is not part of KiF
binary)
use('kiflmdb');
//We declare an lmdb variable
lmdb lm;
//We open a directory, which must exist otherwise it
is an error.... The size here is 10000000
lm.open('C:\XIP\Test\lmdb\mydb',1000000,0,0);
//we create a table
lm.create("",0);
//This instruction is used so that each association
lm[key]=v is automatically stored
//If you deactivate it, you will need to commit, which
of course is usually the best solution as selfcommit is much slower....
//lm.selfcommit(true);
//We store our values in the table....
int i;
string
akey;
for
(i=0;i<100;i++) {
akey="K"+i;
lm.put(akey,i,0);
}
//We have two ways to store things, we can use put or
a map
lm["KK"]=-29;
//In all cases, we need to commit, unless for the
above instruction we have set: selfcommit,
//which is not a good idea usually.
lm.commit();
//We look for values, note that you can again use a
map instruction...
println(lm["K3"],lm["KK"]);
//Or you can use 'get', with the flag: MDB_SET_KEY
lm.get("K2",MDB_SET_KEY);
//You can also use iterator among values
iterator it=lm;
//No need for begin, as the above 'get' has already
triggered a loop among values... Hence, the initialization which has been
replaced with a “true” in the for
loop.
for (true;it;it++)
{
//We only display the values we are interested in
if (it.key()!="K2")
break;
println("Key:",it.key());
println("Value:",it.value());
}
//we close our database...
lm.close();
println("Ok");
['3'] ['-29']
Key: K2
Value: 2
Ok
KiF provides
also an encapsulation of the liblinear
library (see http://www.csie.ntu.edu.tw/~cjlin/liblinear/
for more information).
This library is
used to implement classifiers and exposes the following methods.
1. cleandata():
clean internal data
2. crossvalidation():
Relaunch the cross validation with new
parameters. The result is a fmap.
3. load(string inputdata,smap
options,string outputfilename): load your training data with some options. outputfilename is optional. It will be used to store the
final model if provided.
4. loadmodel(string filename):
Load your model. A model can also
automatically be loaded with a constructor.
5. options(smap actions):
Set the training options (see below)
6. predict(fvector labels, vector
data,bool predict_probability,bool infos): Predict from a vector of iftreemap. labels is optional. When it is provided, it is used
to test the predicted label against the target label stored in labels. If infos is true, the first element of this vector is an info map.
7. predictfromfile(string input,bool
predict_probability,bool infos): Predict from a file input. The result is a vector. If infos is true, the first element of this vector is
an info map.
8. savemodel(string
outputfilename): save
your model in a file
9. trainingset(fvector
labels,vector data):
create your training set out of a iftreemap vector.
The
training options should be provided as a smap, with the following keys: s,e,c,p,B,wi,M and
v.
1. 's' type : set type of solver (default 1)
a.
for multiclass classification
0 -- L2-regularized logistic regression (primal)
1 -- L2-regularized L2-loss support vector classification (dual)
2 -- L2-regularized L2-loss support vector classification (primal)
3 -- L2-regularized L1-loss support vector classification (dual)
4 -- support vector classification by Crammer and Singer
5 -- L1-regularized L2-loss support vector classification
6 -- L1-regularized logistic regression
7 -- L2-regularized logistic regression (dual)
b.
for regression
11 -- L2-regularized L2-loss support vector regression (primal)
12 -- L2-regularized L2-loss support vector regression (dual)
13 -- L2-regularized L1-loss support vector regression (dual)
2. 'c' cost : set the parameter C (default 1)
3. 'p' epsilon : set the epsilon in loss function of SVR (default 0.1)
4. 'e' epsilon : set tolerance of termination criterion
a.
's' 0 and 2
|f'(w)|_2 <= eps*min(pos,neg)/l*|f'(w0)|_2,where f is the primal
function and pos/neg are # of positive/negative data (default 0.01)
b.
's' 11
|f'(w)|_2 <= eps*|f'(w0)|_2 (default 0.001)
c.
's' 1, 3, 4, and 7
Dual maximal violation <= eps; similar to libsvm (default 0.1)
d.
's' 5 and 6
|f'(w)|_1 <= eps*min(pos,neg)/l*|f'(w0)|_1,where f is the primal function
(default 0.01)
e.
's' 12 and 13
|f'(alpha)|_1 <= eps |f'(alpha0)|, where f is the dual function
(default 0.1)
5.
'B' bias : if bias >= 0, instance x becomes [x; bias]; if < 0, no bias term
added (default -1)
6.
'wi' weight: weights adjust the parameter C of different classes. ‘i' stands here for an index. A key might
look like “w10” for instance.
7.
8.
'M' type: type of multiclass classification (default 0)
a. 'M' 0: one-versus-all
b.
'M' 1: one-versus-one
9.
'v' n: n-fold
cross validation mode
Note that
it is possible to use the following strings instead of integers for the solver:
·
"L2R_LR" is
0
·
"L2R_L2LOSS_SVC_DUAL"
is 1
·
"L2R_L2LOSS_SVC"
is 2
·
"L2R_L1LOSS_SVC_DUAL"
is 3
·
"MCSVM_CS"
is 4
·
"L1R_L2LOSS_SVC"
is 5
·
"L1R_LR"
is 6
·
"L2R_LR_DUAL"
is 7
·
"L2R_L2LOSS_SVR
= 11" is 8
·
"L2R_L2LOSS_SVR_DUAL"
is 9
·
"L2R_L1LOSS_SVR_DUAL"
is 10
Example:
smap s={'s':'L1R_LR','B':1,'v':9};
These two
methods accept as input two structures. The first one is a fvector, which will contain the so-called labels (float elements),
the second one is a vector of iftreemap.
The two structures should have exactly the same size.
Each
element in the second parameter vector is a ifreemap,
where the key is the index and the value the associated probability. This
structure has been chosen to store sparse vectors.
The two
predict methods output is a vector of maps. The first element is an info smap, which contains some measures
over the whole analysis (such as the “accuracy”). The next elements depending
on the flag: predict_probability are
either the predicted label or a map containing for each line from the input
structure the label with the associated list of probabilities.
{'1':[0.999725,3.66243e-05,4.85055e-06,4.49336e-07,6.43783e-05]}
The key is the chosen label, with the list of its
probabilities.
It is simply the chosen label.
//we load the library
use("kifliblinear");
string
trainFile = "output.dat";
//we declare a liblinear variable
liblinear train;
//We set the options
smap
options ={"c":100, "s":'L2R_LR', "B":1, "e":0.01};
//we load our model, whose training output will be
stored in the model_test file
train.load(trainFile, options, "model_test" );
use("kifliblinear");
//The input file
string
testFile = "trainData.dat";
//a liblinear variable, which is declared with its
model (we could use loadmodel instead)
liblinear predict("model_test");
//The prediction is done from a file
vector
result = predict.predictfromfile(testFile,true);
KiF provides
also an encapsulation of the libsvm library
(see http://www.csie.ntu.edu.tw/~cjlin/libsvm/
for more information).
This library is
used to implement classifiers and exposes the following methods. It uses the
same data schema as liblinear above.
1.
cleandata():
clean internal data
2.
crossvalidation():
Relaunch the cross validation with new
parameters. The result is a fmap.
3. load(string inputdata,smap
options,string outputfilename): load your training data with some options. outputfilename is optional. It will be used to store the
final model if provided.
4.
loadmodel(string
filename): Load
your model. A model can also automatically be loaded with a constructor.
5.
options(smap
actions): Set the
training options (see below)
6. predict(fvector labels, vector
data,bool predict_probability,bool infos): Predict from a vector of iftreemap. labels is optional. When it is provided, it is
used to test the predicted label against the target label stored in labels. If infos is true, the first element of this vector is an info map.
7. predictfromfile(string
input,bool predict_probability,bool infos): Predict from a file input. The result is a
vector. If infos is true, the first
element of this vector is an info map.
8.
savemodel(string
outputfilename): save
your model in a file
9.
scale(string
inputfile,map options): Scale
files along options:
i.
'l': lower_value
ii.
'u': upper_value
iii.
'y': y_lower_value
iv.
'Y': y_upper_value
v.
'r': restore_file_name
vi.
's': save_file_name.
The training
options should be supplied as a map with the following keys and values:
's': svm_type : set type of SVM (default 0)
0 --
C-SVC (multi-class
classification)
1 --
nu-SVC (multi-class
classification)
2 --
one-class SVM
3 -- epsilon-SVR (regression)
4 --
nu-SVR (regression)
't': kernel_type : set type of kernel function (default
2)
0 --
linear: u'*v
1 --
polynomial: (gamma*u'*v + coef0)^degree
2 --
radial basis function: exp(-gamma*|u-v|^2)
3 --
sigmoid: tanh(gamma*u'*v + coef0)
4 --
precomputed kernel (kernel values in training_set_file)
'd': degree : set degree in kernel function (default 3)
'g': gamma : set gamma in kernel function (default
1/num_features)
'r': coef0 : set coef0 in kernel function (default 0)
'c': cost : set the parameter C of C-SVC, epsilon-SVR,
and nu-SVR (default 1)
'n': nu : set the parameter nu of nu-SVC, one-class
SVM, and nu-SVR (default 0.5)
'p': epsilon : set the epsilon in loss function of
epsilon-SVR (default 0.1)
'm': cachesize : set cache memory size in MB (default
100)
'e': epsilon : set tolerance of termination criterion (default
0.001)
'h': shrinking : whether to use the shrinking
heuristics, 0 or 1 (default 1)
'b': probability_estimates : whether to train a SVC or
SVR model for probability estimates, 0 or 1 (default 0)
'wi': weight : set the parameter C of class i to weight*C,
for C-SVC (default 1)
'v': n: n-fold cross validation mode
kifwapiti is an encapsulation of the wapiti
library, which is available on: http://wapiti.limsi.fr
.
kifwapiti provides an efficient implementation of
the CRF method, to do tagging or entity extraction. If you need any information
on the system please refer to the manual on: http://wapiti.limsi.fr/manual.html
kifwapiti exposes the following methods.
1.
loadxip(string
crfmodel, string brown,string mkcls): Loading the CRF files for tweet tagging.
2.
loadmodel(string
crfmodel): Loading
a CRF model.
3.
options(svector
options): Setting
options. See below for the available options. Options should be place in the svector
as used on the command line of wapiti.
4.
train():
Launch
training. Requires options to have been set in advance.
5.
label(vector
words): Launch
labelling for a vector of tokens. Returns
a vector of labels for each token.
6.
analyze(svector
words): Analyze
a sequence of words and returns the predicted tags, according to loadxip files.
7.
lasterror():
Return
the last error, that did occur.
Wapiti
exposes some options to deal with all the possibilities engrained in the
system. Below is a list of these options, which should be supplied as a svector, exactly as these options would
be provided with the command line version of wapiti.
§
Train mode:
·
train [options] [input
data] [model file]
o –me force maxent mode
o –T | --type STRING type of model to train
o –a | --algo STRING training algorithm to use
o –p | --pattern FILE patterns
for extracting features
o –m | --model FILE model
file to preload
o –d | --devel FILE development
datset
o –rstate FILE optimizer state to restore
o –sstate FILE optimizer state to save
o –c | --compact compact model after training
o –t | --nthread INT number
of worker threads
o –j | --jobsize INT job size for worker threads
o –s | --sparse enable sparse forward/backward
o –I | --maxiter INT maximum
number of iterations
o -1 | --rho1 FLOAT l1 penalty
parameter
o -2 | --rho2 FLOAT l2 penalty
parameter
o –o | --objwin INT convergence
window size
o –w | --stopwin INT stop
window size
o –e | --stopeps FLOAT stop
epsilon value
o –clip (l-bfgs) clip gradient
o –histsz INT (l-bfgs) history
size
o –maxls INT (l-bfgs) max
linesearch iters
o --eta0 FLOAT (sgd-l1) learning
rate
o –alpha FLOAT (sgd-l1) exp decay
parameter
o –kappa FLOAT (bcd) stability parameter
o –stpmin FLOAT (rprop) minimum step size
o –stpmax FLOAT (rprop) maximum step size
o –stpinc FLOAT (rprop) step increment factor
o –stpdec FLOAT (rprop) step decrement factor
o –cutoff (rprop) alternate projection
§ Label
mode:
·
label [options] [input data]
[output data]
o --me force maxent mode
o –m | --model FILE model
file to load
o –l | --label output only labels
o –c | --check input is already labeled
o –s | --score add scores to output
o –p | --post label using posteriors
o –n | --nbest INT output n-best list
o --force use forced decoding
Note: loadxip and analyze do not require any
options. They load the necessary file to run the taggers that have been trained
over tweets. If you need to train a “pure” CRF system, do not use these two
methods.
To train
a CRF, you need a text file with annotations, where each line is a token with
its tags separated with a tab.
UNITED STATES NOUN LOCATION_b
SECURITIES NOUN ORGANISATION_i
AND CONJ ORGANISATION_i
EXCHANGE NOUN ORGANISATION_i
COMMISSION NOUN ORGANISATION_i
Washington NOUN ORGANISATION_i
, PUNCT ORGANISATION_i
D.C. NOUN LOCATION_b
20549 DIG NUMBER_b
FORM VERB null
N NOUN null
In this example, we have a token for each line
associated with two different tags.
N.B. The tag “null” in this example is a simple string
that does not have a specific interpretation except for this specific example.
You also a need a “pattern” file, which would be
implemented according to the manual either of CRF++ (on which it is based, see http://taku910.github.io/crfpp/ for
more information) or as described in http://wapiti.limsi.fr/manual.html.
# Unigram
U00:%x[-2,0]
U01:%x[-1,0]
U02:%x[0,0]
U03:%x[1,0]
U04:%x[2,0]
U05:%x[-2,1]
U06:%x[-1,1]
U07:%x[0,1]
U08:%x[1,1]
U09:%x[2,1]
U10:%x[-2,0]/%x[0,0]
U11:%x[-1,0]/%x[0,0]
U12:%x[0,0]/%x[1,0]
U13:%x[0,0]/%x[2,0]
U14:%x[-2,1]/%x[0,1]
U15:%x[-1,1]/%x[0,1]
U16:%x[0,1]/%x[1,1]
U17:%x[0,1]/%x[2,1]
# Bigram
B
Here is a small program, which takes as input a training file and a
pattern file to produce the model that will be used to label entities.
use('kifwapiti');
wapiti tst;
//we are
going to produce our model, based on the pattern file and the training file
svector v=["train","-p","pattern","-1","5","training","model"];
tst.options(v);
tst.train();
The labeling is processed through the method: “label”. In order to use this method, you
must first load the model that you produce through training. The process
consists in sending a list of tokens and receiving as output a vector, with the
same size, containing the corresponding labels. Actually, you need to provide
the system with a list of tokens, each associated to their specific tag (here a
POS). The system will then try to evaluate the final tag for each of them
according to the training set.
use('kifwapiti');
//Our
input...
svector words=['Growth NOUN','& CONJ','Income NOUN',
'Fund NOUN','( PUNCT','Exact ADJ',
'name NOUN','of PREP','registrant NOUN',
'as PREP','specified ADJ','in PREP','charter
NOUN'];
wapiti tst;
//We then
load our model...
tst.loadmodel("model");
//We then
label our vector of tokens
svector res=tst.label(words);
//Which
returns as output a list of tags...
println(res);
The result is :
['ORGANISATION_b','ORGANISATION_i','ORGANISATION_i','ORGANISATION_i','null','null','null','null','null','null','null','null','null']
KiF provides an
encapsulation of word2vec. See https://code.google.com/p/word2vec/ for more information.
With this
library you can both train the system on corpora and use the result through distance or analogy.
1.
accuracy(vector
words,int threshold): Finding
accuracies for a vector of many times 4 words. Return a fmap. If threshold is
not supplied then its value is 30000
2.
analogy(svector
words): Finding
analogies for a group of words. Return a fmap
3.
distance(svector
words): Finding
the distance in a vector of words. Return a fmap.
4.
features():
Return
a map of the vocabulary with their feature values.
5.
initialization(map
m): Initialization
of a word2vec training set
6.
loadmodel(string
filename,bool normalize): Loading
a model
7.
trainmodel(vector
v): Launching
the training. If v is not supplied, then the system utilizes the input file
given in the initialisation options
8.
vocabulary():
Return
a itreemap of the vocabulary covered by the training.
The
options are supplied as a map to the library. These options are exactly the
same as the one expected by the library.
map options={"train":input.txt,"output":output.txt,"cbow":1,
"size": 200,"window":5,"negative":25,"hs":0,
"sample":1e-4,"threads":20,"binary":1,"iter":15};
For a better explanation of these options, please read the appropriate
information on Word2Vec site. The most important options are:
·
“train”: this option should be associated with the
file that will be used as training material.
·
“output”: the value for that key is the output file,
in which the final training model will be stored.
·
“window”: this value defines the number of words taken
into account as a proper context for a given token.
·
“threads”: word2vec utilizes threads to speed up the
process. You can define the number of threads the system can use.
·
“size”: this value defines the size of the vector that
is associated to each token.
·
“iter”: this value defines the number of iterations to
build the model.
Once, these options have been supplied, call initialisation to set them in.
//We will
train our system on input.txt, the result will be stored in output.bin
use("kifword2vec");
word2vec
wrd;
//Window
will be 5 words around the main word.
//Vector
size for each word will be 200
//The
system will use 20 threads to compute the final model
//with 15
iterations.
map options={"train":input.txt,"output":output.bin,"cbow":1,
"size": 200,"window":5,"negative":25,"hs":0,
"sample":1e-4,"threads":20,"binary":1,"iter":15};
wrd.initialization(options);
wrd.trainmodel();
To use a
model, once it has been created, you simply use loadmodel, then you can use either distance, analogy or accuracy.
All these methods returns a list of words with their distance to the words in
the input vectors. The vocabulary against which the words are compared is the
one extracted from the input document. You can have access to all these words
with the function vocabulary.
Example:
use("kifword2vec");
word2vec
wrd;
//we load
the model that was obtained through training
wrd.loadmodel("output.bin");
svector v=[“word”];
fmap res=wrd.distance(v);
Each word extracted from the input document is
associated with a specific vector whose size is supplied at training time with
the option: “size”. In our example,
this size is set to 200.
It is actually possible to extract a specific vector
from the training vocabulary and store it into a specific object: w2vector.
1.
dot(element):
Return
the dot product between two words. Element is either a string or a w2vector.
2.
cosine(element):
Return
the cosine distance between two words. Element is either a string or a w2vector.
3.
distance(element):
Return
the distance between two words. Element is either a string or a w2vector.
4.
threshold(element):
Return
or set the threshold.
5.
norm(element):
Return
the vector norm.
The initialization of a w2vector object is done
requires first that a model has been loaded, then you need to provide both the
token string and a threshold.
use("kifword2vec");
word2vec
wrd;
//we load
the model that was obtained through training
wrd.loadmodel("output.bin");
w2vector w=wrd["tsunami":0.5];
w2vector ww=wrd["earthquake":0.5];
println(w.distance(ww));
The threshold is not mandatory. It is actually used
when you compare two w2vector elements together to see whether they are close.
The threshold is then used to detect whether the distance between the two
elements is superior to that threshold.
In other words:
if (w==ww)… is equivalent to if (w.distance(ww)>=w.threshold())
if (w==ww)
println("ok");
if (w=="earthquake") //we can compare against a simple
string…
println("ok");
It is also possible to retrieve the inner float vector
from a w2vector…
fvector vvv=w;
vvv is:
[0.049775,-0.0498451,-0.0722533,0.0536649,-0.000515156,-0.0947062,0.0294775,-0.0146792,-0.100351,0.0480318,0.071128,0.0268629...]
You can use this property to unify two variables in a
predicate, if the distance between two w2vector
is superior to their threshold.
use("kifword2vec");
word2vec
model;
model.loadmodel("mymodel.bin");
afact("Accident","Fukishima
","tsunami ","accident
").
afact("Incident","Fukushima
","earthquake","incident
").
evaluation(?X) :- afact("Accident",_,?X,_).
comp(?X,?Y) :-
evaluation(?X),afact("Incident",_,?Y,_).
//we then
call our predicates with two w2vector, built on the fly
//Their
threshold is set to 0.3. If we compare a w2vector to a string, then
//the
distance is automatically computed with this string and returns TRUE
//if it
is superior to the w2vect threshold of 0.3.
//Here,
model[“damage”:0.3] will be compared against “accident” and
//“incident”.
vector vres=comp(model["earthquake":0.3],model["damage":0.3]);
FLTK (http://www.fltk.org/
) is a graphical C++ library, which has been implemented for many different
platforms, ranging from Windows to Mac Os. We have embedded FLTK into a KiF
library, in order to enrich the language with some GUI capabilities. The full range of features from FLTK has only
been partially implemented into the KiF library. However, the available methods
are enough to build simple but powerful interfaces.
The name of the library is: kifltk.dll (.so)
a)
We have linked KiF with FLTK 1.3.0.
b)
The associate function methodology has
been extended to most graphical objects.
Most of the
objects which are described in the next section share the following methods,
which are used to handle the label associated to a window, a box, an input etc…
These methods, when used without any parameters,
return their current value.
1.
align(int a): define the label alignement (see below)
2.
backgroundcolor(int color): set or return the background color
3.
coords(): return a vector of the widget coordinates
4.
coords(int x,int y,int w,int h): set the widget
coordinates. It also accepts a vector instead of the four values.
5.
created(): return true if the object has been correctly created
6.
hide(): hide a widget
7.
label(string txt): set the label with a
new text
8.
labelcolor(int c): set or return the
font color of the label
9.
labelfont(int f): set or return the font of the label
10.
labelsize(int i): set or return the font size of the label
11.
labeltype(int i): set or return the font type of the label
(see below for a description of the different types)
12.
selectioncolor(int color):
set or return the widget selected color
13.
show(): show a widget
14.
timeout(float f): set the timeout of an
object within a thread.
15.
tooltip(string txt): associate a widget
with a tooltip
FL_NORMAL_LABEL
FL_NO_LABEL
FL_SHADOW_LABEL
FL_ENGRAVED_LABEL
FL_EMBOSSED_LABEL
FL_ALIGN_CENTER
FL_ALIGN_TOP
FL_ALIGN_BOTTOM
FL_ALIGN_LEFT
FL_ALIGN_RIGHT
FL_ALIGN_INSIDE
FL_ALIGN_TEXT_OVER_IMAGE
FL_ALIGN_IMAGE_OVER_TEXT
FL_ALIGN_CLIP
FL_ALIGN_WRAP
FL_ALIGN_IMAGE_NEXT_TO_TEXT
FL_ALIGN_TEXT_NEXT_TO_IMAGE
FL_ALIGN_IMAGE_BACKDROP
FL_ALIGN_TOP_LEFT
FL_ALIGN_TOP_RIGHT
FL_ALIGN_BOTTOM_LEFT
FL_ALIGN_BOTTOM_RIGHT
FL_ALIGN_LEFT_TOP
FL_ALIGN_RIGHT_TOP
FL_ALIGN_LEFT_BOTTOM
FL_ALIGN_RIGHT_BOTTOM
FL_ALIGN_NOWRAP
FL_ALIGN_POSITION_MASK
FL_ALIGN_IMAGE_MASK
This object is
used to load an image from a GIF or a JPEG file, which can then be used with a
window object or a button object, through the method image.
1.
loadjpeg(string filename): load a JPEG image
2.
loadgif(string filename): load a GIF image
Once a wimage object has been declared, you can load your file and use
this object in the different image
methods when available.
The window type is
the entry point of this graphical library. It exposes many methods, which can
be used to display boxes, buttons, sliders etc.
1.
alert(string msg): Pop up window to display an alert
2.
arc(float x,float y,float rad,float a1,float a2): Draw an arc
3.
arc(int x,int y,int x1, int y1, float a1, float a2): Draw an arc
4.
ask(string msg,string buttonmsg2,string buttonmsg1,…): Pop up window to pose
a question, return 0,1,2 according to which button was pressed up to 4 buttons.
5.
begin(int x,int y,int w, int h,string title): Create a window and
begin initialisation, w and h are optional
6.
border(bool b): If true add or remove borders. With no
parameter return if the window has borders
7.
circle(int x,int y,int r,int color): Draw a circle.
‘color’ is optional. It defines which color will be used to fill the
circle up.
8.
close(): close the window
9.
create(int x,int y,int w, int h,string title): Create a window
without widgets, w and h are optional
10. cursor(int cursortype,int
color1, int color2): Set the cursor shape. See below for a list of cursor shapes.
11. drawcolor(int c): set the color for the
next drawings
12. drawtext(string l,int
x,int y): Put a text at position x,y
13. end(): end creation
14.
flush(): force a redraw of all windows.
15. font(int f,int sz): Set the font name and
its size
16. fontnumber(): return the number of
available fonts.
17. get(string msg): display a window to get a
value
18. getfont(int num): get font name.
19. getfontsizes(int num): return a vector of
available font sizes.
20. hide(bool v): hide the window if v is
true, show it otherwise
21. image(wimage image,int x,
int y, int w, int h): Display an image
22. initializefonts(): load fonts from system.
Return the number of available fonts (see below for an example)
23. line(int x,int y,int x1,
int y1,int x2, int y2): Draw a line between points, x2 and y2 are optional
24. lineshape(int type,int
width): Select the line shape and its thikness
25. lock(): FLTK lock
26. menu(vector,int x,int
y,int w, int h): initialize a menu with its callback functions
27. modal(bool b): If true make the
window modal, with no parameter, it returns if the window is modal.
28. onclose(function,object): define a callback function
to be called when the window is closed (see below)
29. onkey(int action,
function,object): Set the callback function on a keyboard action with a given object as
parameter
30. onmouse(int action,
function,object): Set the callback function on a mouse action with a given object as
parameter
31. ontime(function,float
t,object o): define a callback function to be called every t second (see
below)
32. password(string
msg): Display a window to type in a password
33. pie(int x,int y,int x1,
int y1, float a1, float a2): Draw a pie
34. plot(fvector xy,int
thickness,fvector landmark): Plot a graph from a table of successive x,y points according
to window size. If thickness===0 then points are continuously plotted, else it defines
the diameter of the point. Return a float vector which is used with plotcoords.
The landmark vector is optional, it is has the following structure: [XminWindow,YminWindow,XmaxWindow,YmaxWindow,XminValue,YminValue,XmaxValue,YmaxValue,incX,incY,thickness].
Only the two first values are mandatory, however the vector can only have the
following size: 4,8,10,11.
35. plotcoords(fvector landmark,float
x,float y): Compute the coordinates of a point(x,y) according to the
previous scale computed with plot. Returns a vector of two elements [xs,ys]
corresponding to the screen coordinates in the current window.
36. point(int x,int y): Draw a pixel
37. polygon(int x,int y,int
x1, int y1,int x2, int y2, int x3, int y3): Draw a polygon, x3
and y3 are optional
38. popclip(): Release a clip region
39. position(): return a vector of the
x,y position of the window
40. position(int x,int y): position the window at
the coordinates x,y
41. pushclip(int x,int y,int
w, int h): Insert a clip region,
with the following coordinates
42. rectangle(int x,int y,int
w, int h, int c): Draw a rectangle with optional color c
43. rectanglefill(int x,int
y,int w, int h, int c): Fill a rectangle with optional color c
44. redraw(): Redraw the window
45. resizable(object): make the object
resizable
46. rgbcolor(int color): return a vector of
the color decomposition of into RGB components
47. rgbcolor(int r,int g,int
b): return
the int corresponding to the
combination of RGB components.
48. rgbcolor(vector rgb): return the int corresponding to the combination of RGB
components, which are stored in a vector.
49. run(): Launch the GUI
50. size(): return a 4 values vector
of the window size
51. size(int x,int y,int w,int
h): resize the window
52. sizerange(int minw,int minh,
int maxw,int maxh): define range in which the size of the window can evolve
53. textsize(string l): Return a map with w
and h as key to denote width and height of the string in pixels
54. unlock(): FLTK unlock
It is possible to intercept the closing
of a window with a special callback
function, which should return true,
if the action of closing the window is to be processed.
The function should have the following
form:
function closing(window w,myobject o);
If this function returns false then the action of closing the
window is stopped.
function closing(window
w, bool close) {
if (close==false) {
println(“We cannot close this window”);
return(false);
}
return(true);
}
//We first
declare our window object
window w;
bool closed=false;
//We then
begin our window instanciation
w.begin(300,200,1300,150,"Modification");
w.onclose(closing,closed);
It is possible to define a function
that is called every tth second. This function must have the
following parameters:
function
timeout_callback(window w, object o);
If this
function returns 0, then the clock is stopped. However, if this function
returns any other float value, then the clock is reset and a new call is
scheduled.
//the
callback function
function temps(window
w,self n) {
println("Ok");
return(0.1);
}
window w;
w.begin(40,40,400,500,"Browsing");
w.ontime(temps,0.1,null);
w.end();
w.run();
Kifltk library implements a few simple
ways to select colors. Colors are implemented as int.
FL_GRAY0
FL_DARK3
FL_DARK2
FL_DARK1
FL_LIGHT1
FL_LIGHT2
FL_LIGHT3
FL_BLACK
FL_RED
FL_GREEN
FL_YELLOW
FL_BLUE
FL_MAGENTA
FL_CYAN
FL_DARK_RED
FL_DARK_GREEN
FL_DARK_YELLOW
FL_DARK_BLUE
FL_DARK_MAGENTA
FL_DARK_CYAN
FL_WHITE
It is also
possible to define your own colors with an RGB encoding. RGB stands for Red
Blue Green.
KiF provides
the following method at this effect: rgbcolor.
a)
vector
rgb=rgbcolor(int c): this method returns a vector
containing the decomposition of that color c into its RGB components.
b)
int
c=rgbcolor(vector rgb): this method takes as input a vector
containing the RGB encoding and returns the equivalent color.
c)
int
c=rgbcolor(int r,int g,int b): same as above, but
takes the three components individually.
Each component is a value in: [0..255]...
Kiflitk provides the following font
codes:
FL_HELVETICA
FL_HELVETICA_BOLD
FL_HELVETICA_ITALIC
FL_HELVETICA_BOLD_ITALIC
FL_COURIER
FL_COURIER_BOLD
FL_COURIER_ITALIC
FL_COURIER_BOLD_ITALIC
FL_TIMES
FL_TIMES_BOLD
FL_TIMES_ITALIC
FL_TIMES_BOLD_ITALIC
FL_SYMBOL
FL_SCREEN
FL_SCREEN_BOLD
FL_ZAPF_DINGBATS
FL_FREE_FONT
FL_BOLD
FL_ITALIC
FL_BOLD_ITALIC
The following
example shows how all available fonts can be loaded from the current system to
enrich the list above.
window w;
map styles;
editor wo;
int ifont=0;
//we modify the current style of
the editor to reflect the selected font
function fontchoice(int
idfont) {
//we create a new default style, whose font id is i
styles["#"]=[FL_BLACK,idfont,16];
wo.addstyle(styles);
//we modify the title of the editor label to reflect the
current font name
wo.label(w.getfont(idfont)+":"+idfont);
//to be sure that the label will
be correctly we re-display the whole window
w.redraw();
}
//Whenever the "next"
button is pressed we change our current font
function change(button b,int i) {
fontchoice(ifont);
ifont++;
}
button b(ifont) with change;
w.begin(50,50,800,500,"Font Display");
w.sizerange(10,10,0,0);
int i;
//First we load our font from the
system, to see which fonts are available
int nb=w.initializefonts();
wo.create(70,30,730,460,"Fonts");
//we use a default and available
anywhere font
styles["#"]=[FL_BLACK,FL_HELVETICA,16];
wo.addstyle(styles);
//we loop among all available
fonts to display both their name
//and their available sizes. [0]
means that every size is available
string s,fonts;
vector v;
for (i=0;i<nb;i++) {
if (fonts!="")
fonts+="\r";
s=w.getfont(i);
v=w.getfontsizes(i);
fonts+=i+":"+s+"="+v;
}
//we store these names as the
content of the editor
wo.value(fonts);
//the next button
b.create(10,10,40,30,FL_Regular,FL_NORMAL_BUTTON,"Next");
w.end();
w.resizable(wo);
w.run();
Kifltk provides the following values as
line shapes:
FL_SOLID;
FL_DASH;
FL_DOT
FL_DASHDOT
FL_DASHDOTDOT
FL_CAP_FLAT
FL_CAP_ROUND
FL_CAP_SQUARE
FL_JOIN_MITER
FL_JOIN_ROUND
FL_JOIN_BEVEL
FL_CURSOR_DEFAULT: the default cursor, usually an arrow.
FL_CURSOR_ARROW:
an arrow pointer.
FL_CURSOR_CROSS:
crosshair.
FL_CURSOR_WAIT:
watch or hourglass.
FL_CURSOR_INSERT:
I-beam.
FL_CURSOR_HAND:
hand (up arrow on MSWindows).
FL_CURSOR_HELP:
question mark.
FL_CURSOR_MOVE:
4-pointed arrow.
FL_CURSOR_NS:
up/down arrow.
FL_CURSOR_WE:
left/right arrow.
FL_CURSOR_NWSE:
diagonal arrow.
FL_CURSOR_NESW:
diagonal arrow.
FL_CURSOR_NONE:
invisible.
FL_CURSOR_N:
for back compatibility.
FL_CURSOR_NE:
for back compatibility.
FL_CURSOR_E:
for back compatibility.
FL_CURSOR_SE:
for back compatibility.
FL_CURSOR_S:
for back compatibility.
FL_CURSOR_SW:
for back compatibility.
FL_CURSOR_W:
for back compatibility.
FL_CURSOR_NW:
for back compatibility.
The philosophy in FLTK is to open a
window object, to fill it with as many widgets as you wish and then to close
it. Once, the window is ready, you simply run
it to launch it.
//We first
declare our window object
window w;
//We then
begin our window instanciation
w.begin(300,200,1300,150,"Modification");
//We want
our window to be resizable
w.sizerange(10,20,0,0);
//we create
our winput, which is placed within the current window
txt.create(200,20,1000,50,true,"Selection");
//no more
object, we end the session
w.end();
//we then
launch our window
w.run();
If we do not want to store any widgets
in our window, we can replace a call to begin
with a final end, with create.
If you need to draw things, such as
lines or circles, then in that case, you must provide the window with a new drawing
function.
In KiF, this function is provided
through a simple with keyword,
together with the object, which will be passed to the drawing function.
window wnd(object) with callback_window;
This declaration requires some
explanations. First, the “with”
introduces the new display function,
which the window will use for its drawings. If a redraw is applied to this window,
then this function will be automatically called. Second, object is the variable that will be automatically passed to the
associate function, when this function is called.
The associate function must expose the
following signature:
function
callback_window(window w, type o) {…}
w
is our current window, while o is the object, which was declared with the window. This function
should be a sequence of drawing methods, as the one described above.
//A small
frame to record our data
frame mycoord {
int color;
int x,y;
function _initial() {
color=FL_RED;
x=10;
y=10;
}
}
//we
declare our object, which will record our data
mycoord coords;
//our new
display...
//Every
time the window will be modified, this function will be called with a mycood
object
function display(window
w,mycoord o) {
//we select our color, which
will be apply to all objects that follow
w.drawcolor(o.color);
//a different line shape
w.lineshape(FL_DASH,10);
//we draw a rectangle
w.rectangle(o.x,o.y,250,250);
//with some text...
w.drawtext("TEST",100,100);
}
//we
declare our window together with its associated drawing function and the object
coords
window wnd(coords) with
display;
//We do not
need any widgets
wnd.create(100,100,300,300,"Drawing");
wnd.run();
It is also possible to track the
different mouse actions through a callback function. The method mouse has been provided at this effect. It
associates a mouse action with a call to a specific callback function:
onmouse(action,callback,myobject);
1) action
must be one of the following values:
FL_PUSH:
when a button has been pushed
FL_RELEASE:
when a button has been released
FL_MOVE:
when the mouse moves
FL_DRAG:
when the mouse is dragged
FL_MOUSEWHEEL:
when the mouse wheel is moved
2) The
callback function must have the following signature:
function callback_mouse(window w, map coords, type myobject);
The first parameter is the window
itself. The second parameter is a map with the following keys:
coords[“button”] the
value of the last button that was pushed (1,2 or 3)
coords[“x”] the
X coordinate within the window of the mouse
coords[“y”] the
Y coordinate within the window of the mouse
coords[“xroot”] the
mouse absolute X coordinate
coords[“yroot”] the
mouse absolute Y coordinate
coords[“wheelx”] the
mouse wheel increment on X
coords[“wheely”] the
mouse wheel increment on Y
3) myobject
is the object that will be passed to the callback function
//we
declare our object, which will record our data
mycoord coords;
//our new
display...
//Every
time the window will be modified, this function will be called with a mycood
object
function display(window
w,mycoord o) {
//we select our color, which
will be apply to all objects that follow
w.drawcolor(o.color);
//a different line shape
w.lineshape(FL_DASH,10);
//we draw a rectangle
w.rectangle(o.x,o.y,250,250);
//with some text...
w.drawtext("TEST",100,100);
}
//This
function will be called for every single move the mouse, the mycoord object
//is the
same as the one that is associated with our window
function move(window
w,map mousecoord,mycoord o) {
//we then use the mouse
coordinates to position our rectangle
o.x=mousecoord["x"];
o.y=mousecoord["y"];
//we then redraw our window...
w.redraw();
}
//we
declare our window together with its associated drawing function and the object
coord
window wnd(coords) with
display;
//We need
to instanciate the mouse callback
wnd.begin(100,100,300,300,"Drawing");
//the
window will be resizable
wnd.sizerange(10,10,0,0);
//we add a
mouse callback, every MOVE of the mouse will
//trigger a
call to “move”. We share the same object coords with the window
wnd.onmouse(FL_MOVE,move,coords);
//the end...
wnd.end();
wnd.run();
fvector fxy;
//first we compute a graph
and we store the values in fxy.
//even positions correspond
to x values
//odd positions correspond
to y values
function mypoints() {
float x,y;
for (x in <-20,20,0.1>) {
y=x*x*x-10;
fxy.push(x);
fxy.push(y);
}
}
//This function will plot
our graph
function graph(window w,self o) {
w.drawcolor(FL_BLACK);
//We plot our graph, which returns some values, with
the following
//interpretation: [maxWindowX,maxWindowY,minxValue,minyValue,
//maxXValue,maxYValue,incrementX,incrementY]
fvector landmarks=w.plot(fxy,0);
//We then compute the 0,0 coordinates in this new
dimension
fvector axes=w.plotcoords(landmarks,0,0);
//We draw the axes
w.line(axes[0],0,axes[0],landmarks[3]);
w.line(0,axes[1],landmarks[2],axes[1]);
}
mypoints();
window w with graph;
w.begin(30,30,1000,800,"Graph");
w.sizerange(30,30,2000,2000);
w.end();
w.run();
It is also possible to associate a
keyboard action with a callback function. The function to be used in this case
is:
onkey(action,callback,myobject);
1) action
must be one of the following values:
FL_KEYUP:
when a key is pushed
FL_KEYDOWN:
when a key is released
2) The
callback function must have the following signature:
function callback_key(window w, string skey,int ikey,int
combine, myobject object);
The first parameter is the window
itself. The second parameter is the text matching the key that was pressed, the
third parameter is the key code, the fourth one is a combination of all command
keys that were pressed together with the current key and the last one the
object that was provided with the key
function.
3) object
is the object that will be passed to the callback function
The combination value is a binary coded integer with
the following possible values:
·
1=the
ctrl-key was pressed
·
2=the
alt-key was pressed
·
4=the
command-key was pressed
·
8=the
shift-key was pressed
//we
declare our window together with its associated drawing function and the object
function pushed(window
w,string skey,int ikey,int comb,mycoord o) {
//If the
key which is pushed is “J”, then we move our rectangle by 10 pixels up and down
if (skey=="J") {
o.x+=10;
o.y+=10;
//we redraw the whole stuff, so that the coordinates
are
//taken into account
w.redraw();
}
}
window wnd(coords) with
display;
//We need
to instanciate the mouse callback
wnd.begin(100,100,300,300,"Drawing");
//the window
will be resizable
wnd.sizerange(10,10,0,0);
//we add a
mouse callback, every MOVE of the mouse will
//trigger a
call to “move”. We share the same object coords with the window
wnd.onkey(FL_PUSH,pushed,coords);
//the
end...
wnd.end();
wnd.run();
Adding a menu to a window requires a
little more work than for the other elements of the interface. A menu is
composed of a series of top menu items, and for each of these top menu items,
you must provide a specific description of the sub-menus. Each sub-menu is also
associated with a callback function, whose signature must match the following:
function callback_menu(window w,myobj obj);
where obj is an object provided by the user, within the sub-menu
description.
A menu item is described through a
vector, where the first element is the menu item name, followed with a series
of vectors, where each element is a sub-menu.
vector menu;
menu.push(["&File",["&New
File",[FL_COMMAND,"o"],cmenu1,obj,true],
["&Open File",[FL_COMMAND,"i"],cmenu2,obj,false]]);
menu.push(["&Edit",["Cu&t",[FL_COMMAND,"x"],cmenu4,obj,true],
["&Copy",[FL_COMMAND,"c"],cmenu3,obj,false]]);
In the example above, we add two menu
items, whose name are File and Edit, with for each two sub-menus.
A sub-menu item comprises the following
fields:
a)
Its name: “&New File”
b)
Then a combination of keys, which might
trigger the sub-menu base either on the key code or associated with one of the
following values:
FL_SHIFT SHIFT
FL_CAPS_LOCK CAPS
Lock
FL_CTRL CONTROL (see FL_CONTROL)
FL_ALT ALT key
FL_NUM_LOCK NUM LOCK
FL_SCROLL_LOCK SCROLL
Lock
FL_COMMAND COMMAND (see Mac OS)
FL_CONTROL Equivalent to FL_CTRL
c)
The callback function itself
d)
The associated object, which is passed
to the callback
e)
A Boolean value to add a sub-menu
separator.
Once this vector has been described,
you can use the menu method in window
to load it: w.menu(menu,5,5,100,20);
Since FLTK is event-based, animation
can be done with a proper function. The only way is to use a thread, which will
run on its own, independently from the window environment.
//A small
frame to record our data
frame mycoord {
int x,y;
function _initial() {
x=0;
y=0;
}
}
//we
declare our object, which will record our data
mycoord coords;
bool first=true;
//our new
display...
//Every
time the window will be modified, this function will be called with a mycood
object
function display(window
w,mycoord o) {
if (first) {
w.drawcolor(FL_RED);
w.drawtext("Press
T",20,20);
}
else {
//we select
our color, which will be apply to all objects that follow
w.cursorstyle(FL_CURSOR_CROSS,FL_BLACK,FL_WHITE);
w.drawcolor(FL_RED);
w.rectangle(o.x,o.y,60,60);
//with some
text...
w.drawtext("TEST",o.x+20,o.y+20);
}
}
//Once
triggered, this thread will increment the coordinates and forces a redraw of
the window for each new value.
thread bouge(window
wnd) {
while (true) {
coords.x++;
coords.y++;
wnd.redraw();
}
}
function pressed(window
w,string skey,int ikey,int comb,mycoord o) {
//If you press T then the
rectangle will start moving...
if (skey=="T") {
first=false;
bouge(w);
}
}
//we
declare our window together with its associated drawing function and the object
coord
window wnd(coords) with
display;
//We need
to instanciate the mouse call back
wnd.begin(100,100,1300,900,"Drawing");
wnd.sizerange(10,10,0,0);
//we add a
mouse call back
wnd.onkey(FL_KEYUP,pressed,coords);
wnd.end();
wnd.run();
int nb=0;
frame mycoord {
int color;
int x,y,ix,iy;
//common means
that these values are common to all objects
common int maxx,maxy;
int idx;
function _initial(int
xx,int yy) {
color=FL_RED;
x=xx;
y=yy;
ix=1;
iy=1;
idx=nb;
nb++;
}
function Idx() {
return(idx);
}
function increment() {
x+=ix;
if
(x>=maxx)
ix=-1;
else
if (x<=0)
ix=1;
y+=iy;
if
(y>=maxy)
iy=-1;
else
if (y<=0)
iy=1;
}
function raz() {
x=10;
y=10;
}
function X() {
return(x);
}
function Y() {
return(y);
}
function string() {
string s=x+","+y;
return(s);
}
}
//This
thread increments the coordinates of the ball
thread move(window
w,mycoord ballecoords) {
while (true) {
ballecoords.increment();
//we redraw
our window to take these new coordinates into account
try {
w.redraw();
}
catch()
{
return;
}
}
}
//We create
a base object to handle the window size
mycoord
basecoords(0,0);
basecoords.maxx=500;
basecoords.maxy=300;
int debx,deby;
vector balles;
function clicked(button
b,window w) {
//the initial positions of
the ball are random
debx=random()*500;
deby=random()*300;
//we create our new ball
mycoord ballecoords(debx,deby);
//we keep a
track of these coordinates in a vector
balles.push(ballecoords);
move(w,ballecoords);
}
function display(window
w,vector bs) {
//we select our color,
which will be apply to all objects that follow
self o;
int i;
//for each ball, we draw a
simple circle with its number in the middle
for (o in bs) {
w.circle(o.X(),o.Y(),10);
w.drawtext(o.Idx(),o.X()-5,o.Y()+2);
}
//if the dimensions of the
windows have changed, we use them as new constraints...
basecoords.maxx=w.coords()[2];
basecoords.maxy=w.coords()[3];
}
//we
declare our window together with its associated drawing function and the object
coord
window wnd(balles) with
display;
//We need
to instanciate the mouse call back
wnd.begin(100,50,500,300,"Drawing");
wnd.sizerange(10,10,0,0);
button b(wnd) with
clicked;
b.create(10,10,20,20,FL_Regular,FL_NORMAL_BUTTON,"Ok");
wnd.end();
wnd.run();
It is possible to create windows within
a thread but with some specific precautions. FLTK does not allow the creation
of windows within a thread per se, however a message mechanism is available
which can be used to post window creation or enrichment requests.
First, a lock should be set around the
window creation itself to avoid problems.
Second, a timeout should also be
defined to avoid any inner locking when the creation of a window fails.
Third, if any problem occurs during the
widget creation, then the window under
scope must be closed.
Finally, whenever a window is moved or
modified by a user, this might result into a momentary freeze of other thread
display, since the display and update of windows can only be done within the
main thread.
int px=300;
int py=400;
int nb=1;
//This
thread will display a counter
thread bouge() {
int i=0;
window wx;
woutput wo;
string err;
//We
initialize our main window with a timeout that will be shared by all
sub-objects
wx.timeout(0.1);
//Our main lock, so that
only one thread can create a window at a time
lock("creation");
try {
wx.begin(px,py,250,100,"ICI:"+nb);
wo.create(50,20,120,30,true,"Valeur");
wx.end();
px+=300;
nb++;
if
(px>=1800) {
px=300;
py+=150;
}
}
catch(err) {
//Any errors, we stop. VERY IMPORTANT, we close the
window
if
(wx.created())
wx.close();
unlock("creation");
return;
}
//We release our lock, so
other windows can also be created
unlock("creation");
//We set a different time
out for the counter display
wo.timeout(1);
while (true) {
i++;
try {
wo.value(i);
}
catch(err)
{
//If it is a time out we carry on
if ("Time out"
in err)
continue;
//Else we clean our slate back
if (wx.created())
wx.close();
return;
}
}
}
function pressed(button
b,self n) {
bouge();
}
//we
declare our window together with its associated drawing function and the object
coord
window wnd;
//We need
to instanciate the mouse call back
wnd.begin(100,50,500,300,"Drawing");
button b with pressed;
b.create(430,20,60,60,FL_Regular,FL_NORMAL_BUTTON,"Ok");
wnd.sizerange(10,10,0,0);
//we add a
mouse call back
wnd.end();
wnd.run();
The browser object defines a box in which
strings can be displayed and if necessary selected as a list.
1.
add(label): Add a string to the browser
2.
clear(): Clear the browser from all values
3.
columnchar(): Return the column char separator.
4.
columnchar(string): Set the column char separator
5.
create(x,y,w,h,label): Create a browser
6.
deselect(): Deselect all items
7.
deselect(int i): Deselect item i;
8.
formatchar(): Return the format char.
9.
formatchar(string): Set the format char
10.
insert(l,label): Insert a label before line l
11.
load(filename): Load a file into the browser
12.
select(): Return selected string.
13.
select(int i): Return string at position i.
14.
size(): Return the number of values within the browser
15.
value(): return the current selected value as an index
The only way to use browser in
selection mode is to associate it with a callback function whose signature must
match the following:
function browser_callback(browser
b,myobject o);
A callback function is declared with
“with”.
//the
callback function
function avec(browser
b,self n) {
println("Selection:",b.select(),b.value());
}
window w;
w.begin(40,40,400,500,"Browsing");
browser b with avec;
b.create(10,10,100,150,"Test");
b.add("first");
b.add("second");
b.add("third");
b.add("fourth");
w.end();
w.run();
These two
objects are used to handle a tree, which is clickable. The first object is the
tree object itself, which is composed of a set of wtreeitem.
The object
which is displayed is a hierarchy of nodes, which can each be selected through
a callback function.
1.
add(string path): Add a tree item and return the new wtreeitem
2.
add(wtreeitem e,string n): Add a tree item after e and return the new
wtreeitem.
3.
clear(): Delete the tree items
4.
clicked(): Return the element that was clicked.
5.
close(string path): Close the element.
6.
close(wtreeitem e): Close the element.
7.
connectorcolor(int c): Set or return the connector color.
8.
connectorstyle(int s): Set or return the connector style (see below)
9.
connectorwidth(int s): Set or return the connector width.
10. create(int x,int y,int h,int
w,string label): Create a tree
11. find(string path): Return the element
matching the path.
12. first(): Return the first element.
13. insert(wtreeitem e,string
label,int pos): Insert an element after e with label at position pos in the
children list.
14. insertabove(wtreeitem
e,string label): Insert an element above e with label.
15. isclosed(string path): Return true if element is
closed.
16. isclosed(wtreeitem e): Return true if element is
closed.
17. itembgcolor(int c): Set or return the item
background color.
18. itemfgcolor(int c): Set or return the
foreground color.
19. itemfont(int c): Set or return the item
font.
20. itemsize(int c): Set or return the item
font size.
21. last(): Return the last element as
a wtreeitem
22. marginleft(int s): Set or Get the amount of
white space (in pixels) that should appear between the widget's left border and
the tree's contents.
23. margintop(int s): Set or Get the amount of
white space (in pixels) that should appear between the widget's top border and
the top of the tree's contents.
24. next(wtreeitem e): Return the next element after
e as a wtreeitem.
25. open(string path): Open the element.
26. open(wtreeitem e): Open the element.
27. previous(wtreeitem e): Return the previous
element before e as a wtreeitem.
28. remove(wtreeitem e): Remove the element e from
the tree.
29. root(): Return the root element as
a wtreeitem.
30. rootlabel(string r): Set the root label.
31. selectmode(int s): Set or return the
selection mode (see below)
32. sortorder(int s): Set or return the sort
order (see below)
1.
activate(): Activate the current element.
2.
bgcolor(int c): Set or return the item background color.
3.
child(int i): Return the child element at position i.
4.
children(): Return number of children.
5.
clean(): Remove the object associated through value.
6.
deactivate(): Deactivate the current element.
7.
depth(): Return the depth of the item.
8.
fgcolor(int c): Set or return the foreground color.
9.
font(int c): Set or return the item font.
10. fontsize(int c): Set or return the item
font size.
11. isactive(): Return true if element is
active.
12. isclosed(): Return true if element is
closed.
13. isopen(): Return true if element is
open.
14. isroot(): Return true if element is
root.
15. isselected(): Return true if element is
selected.
16. label(): Return the item label.
17. next(): Return the next element.
18. parent(): Return the last element.
19. previous(): Return the previous
element.
20. value(): Return the value
associated with the object.
21. value(object): Associate the item with a
value.
It is possible to associate a wtree object with a callback. This
callback must have the following signature:
function wtree_callback(wtree t,wtreeitem i,int reason,myobject o);
This function will be called each time
an item will be selected from the tree. Reason is one of the following values:
FL_TREE_REASON_NONE : unknown
reason
FL_TREE_REASON_SELECTED : an item
was selected
FL_TREE_REASON_DESELECTED :an item was de-selected
FL_TREE_REASON_OPENED : an item
was opened
FL_TREE_REASON_CLOSED :an item
was closed
The wtree
object is iteratable.
Certain functions such as add or find requires a path. A path is similar to a unix path and defines a path from the root to the leaf:
Example: “/Root/Top/subnode”
The style of
connectors between nodes is controlled by the following flags:
|
FL_TREE_CONNECTOR_NONE |
Use no lines
connecting items. |
|
FL_TREE_CONNECTOR_DOTTED |
Use dotted
lines connecting items (default) |
|
FL_TREE_CONNECTOR_SOLID |
Use solid
lines connecting items. |
The way nodes are selected in the tree
is controlled by the following flags:
|
FL_TREE_SELECT_NONE |
Nothing selected when items are clicked. |
|
FL_TREE_SELECT_SINGLE |
Single item selected when item is clicked
(default) |
|
FL_TREE_SELECT_MULTI |
Multiple items can be selected by clicking with. |
Items can be added to the tree in an
ordered manner controlled with the following flags:
|
FL_TREE_SORT_NONE |
No sorting; items are added in the order
defined (default). |
|
FL_TREE_SORT_ASCENDING |
Add items in ascending sort order. |
|
FL_TREE_SORT_DESCENDING |
Add items in descending sort order. |
//this
function is called whenever an item is selected or deselected
function avec(wtree
t,wtreeitem i,int reason,self n) {
//we change the size of the
selected element
if
(reason==FL_TREE_REASON_SELECTED)
i.fontsize(20);
else //the deselected element gets its previous size
if
(reason==FL_TREE_REASON_DESELECTED)
i.fontsize(FL_NORMAL_SIZE);
}
window w;
wtree mytree with avec;
wtreeitem ei;
w.begin(40,40,400,500,"Browsing");
mytree.create(20,20,150,250,"Tree");
mytree.rootlabel("Root");
ei=mytree.add("Subroot");
mytree.add(ei,"Test");
mytree.add(ei,"Other");
//we add a
new element as path
mytree.add("/Subroot/New item");
w.end();
//we modify
the font for each element aftward
//This is
equivalent to mytree.itemfont(FL_TIMES_BOLD), before adding elements
iterator it=mytree;
for
(it.begin();it.nend();it.next())
it.value().font(FL_TIMES_BOLD);
w.run();
tree atree={'a':{'b':{'c':1},'d':3}};
function traversetree(tree t,wtree wt,wtreeitem e) {
if (t==null)
return;
wtreeitem x;
//First element is null
if (e==null)
x=wt.add(t);
else
x=wt.add(e,t);
if (t.childnode()!=null)
traversetree(t.childnode(),wt,x);
traversetree(t.nextnode(),wt,e);
}
window w;
wtree mytree;
w.begin(40,40,1000,900,"Display tree");
mytree.create(20,20,950,850,"my tree");
//The root of tree becomes
the root of its representation
mytree.rootlabel(atree);
//we traverse our tree to
build the representation out of it…
traversetree(atree.childnode(),mytree,null);
w.end();
w.run();
The winput object defines an input area in a
window, which can be used in conjunction with a callback function, which will
be called when the zone is dismissed.
1.
i[a]: Extract character from the input at position a
2.
i[a:b]: Extract characters between a and b
3. color(int c): set or return the text color
4.
create(int
x,int y,int w,int h,boolean multiline,string label): Create an input area with multiline if
this parameter is true
5.
font(string
s): set or return the text font
6.
fontsize(int
c): set or return the text font
size
7. insert(string s,int p):
insert s at position p in the input
8.
selection():
return the selected text in the input
9.
value()|(string
v): return the input buffer or
set the initial buffer
10. word(int pos): return the word at position pos
frame block {
//We first declare our window
object
window w;
string final;
function result(winput txt,block bb) {
//we store the content of that
field in a variable for further use
final=txt.value();
}
//We first declare our winput
associated with result
winput txt(this) with result;
function launch() {
//We then begin our
window instanciation
w.begin(300,200,1300,150,"Modification");
//We want our
window to be resizable
w.sizerange(10,20,0,0);
//we create our multiline
winput, which is placed within the current
//window
txt.create(200,20,1000,50,true,"Selection");
//We initialize our
input with some text
txt.value("Some Input
Text");
//The text will be in BLUE
txt.color(FL_BLUE);
//no more object,
we end the session
w.end();
//we want our text to follow the size of the main
window
w.resizable(txt);
//we then launch
our window
w.run();
}
}
//We open a
block
block b;
//which
will display our input
b.launch();
//b.final
contains the string that was keyed in
println("Result:",b.final);
This type is
used to create a specific output in a window. It exposes the following methods:
1. color(int c): set or return the text color
2.
create(int
x,int y,int w,int h,boolean multiline,string label): Create an output area with multiline if
this parameter is true
3.
font(string
s): set or return the text font
4.
fontsize(int
c): set or return the text font
size
5.
value(string
v): initialize the buffer
This type is
used to draw a box in the main window with some texts. It exposes the following
methods:
1.
create(int
x,int y,int w,int h, string label): Create a box with a label
2. type(int boxtype): modify the box type (see below for a list of
box types)
FL_NO_BOX
FL_FLAT_BOX
FL_UP_BOX
FL_DOWN_BOX
FL_UP_FRAME
FL_DOWN_FRAME
FL_THIN_UP_BOX
FL_THIN_DOWN_BOX
FL_THIN_UP_FRAME
FL_THIN_DOWN_FRAME
FL_ENGRAVED_BOX
FL_EMBOSSED_BOX
FL_ENGRAVED_FRAME
FL_EMBOSSED_FRAME
FL_BORDER_BOX
FL_SHADOW_BOX
FL_BORDER_FRAME
FL_SHADOW_FRAME
FL_ROUNDED_BOX
FL_RSHADOW_BOX
FL_ROUNDED_FRAME
FL_RFLAT_BOX
FL_ROUND_UP_BOX
FL_ROUND_DOWN_BOX
FL_DIAMOND_UP_BOX
FL_DIAMOND_DOWN_BOX
FL_OVAL_BOX
FL_OSHADOW_BOX
FL_OVAL_FRAME
FL_OFLAT_BOX
FL_PLASTIC_UP_BOX
FL_PLASTIC_DOWN_BOX
FL_PLASTIC_UP_FRAME
FL_PLASTIC_DOWN_FRAME
FL_PLASTIC_THIN_UP_BOX
FL_PLASTIC_THIN_DOWN_BOX
FL_PLASTIC_ROUND_UP_BOX
FL_PLASTIC_ROUND_DOWN_BOX
FL_GTK_UP_BOX
FL_GTK_DOWN_BOX
FL_GTK_UP_FRAME
FL_GTK_DOWN_FRAME
FL_GTK_THIN_UP_BOX
FL_GTK_THIN_DOWN_BOX
FL_GTK_THIN_UP_FRAME
FL_GTK_THIN_DOWN_FRAME
FL_GTK_ROUND_UP_BOX
FL_GTK_ROUND_DOWN_BOX
FL_FREE_BOXTYPE
The button
object is of course very important as it allows users to communicate with the
GUI. A button must be created in connection with a callback whose signature is
the following:
function callback_button(button b,
myobj obj) {…}
button b(obj) with callback_button;
It
exposes the following methods:
1.
align(int): define the button label alignment
2. color(int code): Set the color of the
button
3.
create(int x,int y,int w,int h,string type,int shape,string
label): Create a button, see below for a list of types and shapes
4.
image(wimage
im,string label,int labelalign):
Use the image as a button image
5. shortcut(string keycode): Set a
shortcut to activate the button from the keyboard (see below for a list of
shortcuts code)
6.
value(): return the value of the
current button
7. when(int when1, int
when2,...): Type of event for a button which triggers the callback (see events
below)
FL_Check
FL_Light
FL_Repeat
FL_Return
FL_Round
FL_Regular
FL_Image
FL_NORMAL_BUTTON
FL_TOGGLE_BUTTON
FL_RADIO_BUTTON
FL_HIDDEN_BUTTON
Below is a list
of events, which can be associated with the callback function.
FL_WHEN_NEVER
FL_WHEN_CHANGED
FL_WHEN_RELEASE
FL_WHEN_RELEASE_ALWAYS
FL_WHEN_ENTER_KEY
FL_WHEN_ENTER_KEY_ALWAYS
Below is the list of shortcuts that can
be associated with a button:
FL_Button
FL_BackSpace
FL_Tab
FL_Enter
FL_Pause
FL_Scroll_Lock
FL_Escape
FL_Home
FL_Left
FL_Up
FL_Right
FL_Down
FL_Page_Up
FL_Page_Down
FL_End
FL_Print
FL_Insert
FL_Menu
FL_Help
FL_Num_Lock
FL_KP
FL_KP_Enter
FL_KP_Last
FL_F_Last
FL_Shift_L
FL_Shift_R
FL_Control_L
FL_Control_R
FL_Caps_Lock
FL_Meta_L
FL_Meta_R
FL_Alt_L
FL_Alt_R
FL_Delete
FL_Delete
frame block {
//We first declare our window
object
window w;
winput txt;
string final;
//When the button is pressed,
this function is called
function gettext(button b,block bb) {
final=txt.value();
w.close();
}
function launch(string ph) {
final=ph;
//We then begin our
window instanciation
w.begin(300,200,1300,150,"Modification");
//We want our
window to be resizable
w.sizerange(10,20,0,0);
//we create our
winput, which is placed within the current window
txt.create(200,20,1000,50,true,"Selection");
txt.value(ph);
//We associate our
button with the method gettext
button b(this)
with gettext;
b.create(1230,20,30,30,FL_Regular,FL_NORMAL_BUTTON
,"Ok");
//no more object,
we end the session
w.end();
w.resizable(txt);
//we then launch
our window
w.run();
}
}
block
b;
b.launch("My
sentence");
First, we need to load an image, then we
create a button with the flag: FL_Image.
wimage myimage;
//We load a
GIF image
image.loadgif(‘c:\...’);
//We
associate our button with the method gettext
button b(this) with gettext;
//We create
pour image button
b.create(1230,20,30,30,FL_Image,FL_NORMAL_BUTTON
,"Ok");
//which we
associate with our button, with an inside label within the image…
b.image(myimage,”Inside”, FL_ALIGN_CENTER);
kifltk provides
a specific type to propose selections in list. This element must be initialized
with a specific menu, which we will describe later on.
It exposes the
following methods.
1. create(int x,int y,int
w,int h,string label): Create an choice
2.
font(string s): set or return the text font
3. fontsize(int c): set or return the
text font size
4.
menu(vector s): Initialize the menu. This should be the last
operation in a wchoice creation.
5.
value(int s): set the choice initialization value
A menu description is a vector of
vectors, each containing three elements.
vmenu=[["First",callback,"1"],["Second",callback,"2"],["Third",callback,"3"]];
A menu item contains, first its name,
then the callback function it is associated with then the object that will be
passed to this callback function.
Menu
Item: [name,callback,object]
The callback function must have the
following signature:
function callback_menu(wchoice c, myobject obj);
This function is called
for each selection from the list.
window w;
function
callback_menu(wchoice c, string s) {
println(s);
}
vector vmenu;
//Our menu
description
vmenu=[["Premier",callback_menu,"RRRR"],["second",callback_menu,"OOOOOO"],["third",callback_menu,"BBBBBBB"]];
wchoice wch;
//we create
our window
w.begin(300,200,1300,500,"Fenetre");
//we create
our choice widget
wch.create(20,420,100,50,"Choix");
wch.fontsize(20);
//This
should be the last operation on the selection list…
wch.menu(vmenu);
w.end();
w.run();
kifltk provides
a specific type to display values in a table and select some elements. This element
table must be created with a callback function (as most widgets), whose
signature is the following:
function callback_table(table x,map values,myobject
obj);
table t(obj) with callback_table;
The values is a map, which contains the
following keys:
“top”: the top row
“bottom”: the bottom row
“left”: the left column
“right”: the right
column
“values”: a map, whose key is a string: “r:c”, with r
as row and c as the column.
This object exposes the following methods:
1. add(int R,int C,string v): Add a value on row R
and column C. The size of the table depends on the number of values added.
2.
boxtype(int boxtype): box type
3. cell(int R,int C): Return the value at row R
and column C
4. cellalign(align): Set the alignment in a
cell
5. cellalignheaderrow(align): Set the alignment in a
row header cell
6. cellalignheadercol(align): Set the alignment in a
column header cell
7. clear(): Clear the table
8. colorbg(int c): set or return the
cell color background
9.
colorfg(int c): set or return the cell color foreground
10. column(): return the number of
columns
11. column(int nb): Define the number of
columns
12.
columnheader(int C,string label): set the label of the
column header for column C
13. columnheaderwidth(int sz): the size in pixel of
the column header
14. columnwidth(int width): Define the column
width in pixel
15. create(int x,int y,int
w,int h,string label): Create a table of objects, and starts adding
16. font(int s): set or return the
text font
17. fontsize(int c): set or return the
text font size
18. row(): return the number of
rows
19. row(int nb,int height): Define the number of
rows
20. rowheader(int R,string
label): set the label of the row header for row R.
21. rowheaderheight(int sz): the size in pixel of
the row header
22. rowheight(int height): Define the row height
in pixel
23. selectioncolor(int color): Color for the
selected elements
24. when(string when): Type of event to
trigger the callback
window w;
function
callback_table(table x,map V,window w) {
println(V);
}
wtable t(w) with
callback_table;
int i,j;
//we create
our window
w.begin(300,200,1300,500,"Fenetre");
//we create
our table
t.create(20,20,500,400,"table");
//with a
certain font size
t.fontsize(12);
//the
selected element will be in blue
t.selectioncolor(FL_BLUE);
//we
populate our table
for (i=0;i<10;i++) {
//including the headers
t.rowheader(i,"R"+i);
t.columnheader(i,"C"+i);
for (j=0;j<10;j++)
//we populate our table with string of the form: R0C9
t.add(i,j,"R"+i+"C"+j);
}
//we define
the size of rows, with their height in pixels,
//after we
populated the table
t.rowheight(20);
//we define
the size of columns, with their width in pixels
t.columnwidth(60);
w.end();
w.run();
Kifltk provides
also a specific type to provide users with an editor, which can be used to
handle text.
A callback can
be associated with an editor, which has a distinctive set of arguments. This
callback is triggered whenever the inside text is modified.
function editorcallback(editor e,int pos, int
ninserted,int ndeleted,int restyled,string del,myobj obj);
This function
is associated with an editor object through a with instruction.
editor e(obj)
with editorcallback;
The arguments
are the following:
editor e: the editor itself
pos: the current cursor position in the document
ninserted: the number of characters which have been
inserted
ndeleted: the number of deleted characters
rstyled: the number of characters whose style has been
modified
del: the characters which have been deleted
obj: the object which has been associated in the with instruction.
This method
exposes the following methods:
1.
addstyle(map styles): Initialize the styles
for text chunks (see below for more details)
2.
annotate(string s,string keystyle,bool matchcase): Each occurrence of s in the text is
assigned the style keystyle. matchcase
is optional.
3.
annotateregexip(string reg,string keystyle): Each string matching the
xip regular expression reg is assigned the style keystyle.
4.
append(string s): append a string at the end
of the editor text
5.
byteposition(int
pos): convert
a character position into a byte position (especially useful in UTF8 strings)
6.
charposition(int
pos): convert
a byte position into a character position (especially useful in UTF8 strings)
7.
color(int c): set or return the text color
8.
copy(): copy selected text to clipboard
9.
copy(string s): copy string s to clipboard
10.
count(string s,int bg,int mx): count the number of
occurrences of s, between bg and mx.
11.
create(int x,int y,int w,int h,string label): Create an editor
12.
cursor():return the current position of the cursor in byte increments.
13.
cursor(int i): move the cursor to the ith bytes.
14.
cursorchar():return the current position of the cursor in character
increments.
15.
cursorstyle(int style):
set the cursor shape (see below)
16.
cut(): cut selected text to clipboard
17.
delete(): delete selected text
18.
e[a:b]: Extract characters between a and b
19.
e[a]: Extract character from the editor at position a
20.
find(string s,int i): find a string in the editor text, starting at
position i.
21.
font(string s): set or return the text font
22. fontsize(int c): set or return the
text font size
23.
getstyle(int start,int end): Return the style for
each character of a chunk of text as a vector.
24. gotoline(int l,bool
highlight): goto line l and highlight it if
true.
25.
highlight():
return
1 or 0 if there is highlighted text in the editor. In the context of a string,
returns the highlighted string
26.
highlight(int start,int end): highlight
the characters between start and end.
27. insert(string s,int pos): insert a string at position pos
28. line(): return the current
line number or the line text itself
29. line(int pos): Return the line
corresponding to pos or the line text itself. This line should be visible.
30.
linebounds(): return a vector with the start position and end position
of the current line in bytes increments.
31.
linebounds(int pos): return a vector with the start position and
end position of the line at pos in bytes increments.
32.
lineboundschar(): return a vector with the start position and end position
of the current line in character increments.
33.
lineboundschar(int pos): return a vector with the start position and
end position of the line at pos in bytes increments.
34.
load(string filename): load the content of a file into the editor.
35. onhscroll(function f,
object o): set the callback when scrolling horizontally (see below for an example)
36. onkey(int action,function
f, object o): set the callback when scrolling vertically (see below for an example)
37. onmouse(int
action,function f, object o): set the callback when handling the mouse
38. onvscroll(function f,
object o): set the callback when scrolling vertically (see below for an example)
39.
paste(): paste selected text to clipboard
40.
rfind(string s,int i): find a string in the editor text, starting at
position I, backward.
41.
save(string filename): save the content of the editor into a file.
42.
selection(): return the selected text in the editor
43.
setstyle(int start,int end, string keystyle): Set a text chunk with
a given style from the style table instatiated with addstyle. (see below for more details)
44.
unhighlight(): remove highlighting
45.
value(string v): return the text in the editor or initialize
the editor
46.
word(int pos): return the word at
position pos
47.
wrap(boolean w): wrap the text within the editor window if w
is true.
KiF provides
different cursor styles:
FL_NORMAL_CURSOR
FL_CARET_CURSOR
FL_DIM_CURSOR
FL_BLOCK_CURSOR
FL_HEAVY_CURSOR
Use cursorstyle
to set it to the proper value.
In the editor,
it is possible to display specific sections of a text with a specific set of
fonts, colors and size. However, in order to achieve this display, FLTK
requires the description of these styles beforehand. Each item is a vector of
three elements: [color,
font, size] associated with a key, which will be used to refer to
that style item.
//A map describing the styles
available within the editor
map m={'#':
[FL_BLACK,FL_COURIER,FL_NORMAL_SIZE ],
'A': [ FL_BLUE,FL_COURIER_BOLD,FL_NORMAL_SIZE ]
'B': [
FL_DARK_GREEN,FL_COURIER_ITALIC,FL_NORMAL_SIZE ],
'C': [ FL_DARK_GREEN,
FL_COURIER_ITALIC,FL_NORMAL_SIZE ],
'D': [ FL_BLUE,FL_COURIER,FL_NORMAL_SIZE ],
'E': [ FL_DARK_RED,FL_COURIER,FL_NORMAL_SIZE ],
'F': [
FL_DARK_RED,FL_COURIER_BOLD,FL_NORMAL_SIZE ],
};
The map should
always have a “#” key which is used to define the default style. If this key is
not provided, an exception will be raised.
Once this map
has been designed, you should pass it to the system with the instruction: addstyle(m).
To use this
style on a section of text, use setstyle
with one the above keys as a way to select the correct style.
//A map describing the styles
available within the editor
map m={'#':
[FL_BLACK,FL_COURIER,FL_NORMAL_SIZE ],
'A': [ FL_BLUE,FL_COURIER_BOLD,FL_NORMAL_SIZE ]};
'B': [
FL_DARK_GREEN,FL_COURIER_ITALIC,FL_NORMAL_SIZE ],
'C': [ FL_DARK_GREEN,
FL_COURIER_ITALIC,FL_NORMAL_SIZE ],
'D': [ FL_BLUE,FL_COURIER,FL_NORMAL_SIZE ],
'E': [ FL_DARK_RED,FL_COURIER,FL_NORMAL_SIZE ],
'F': [ FL_DARK_RED,FL_COURIER_BOLD,FL_NORMAL_SIZE
],
window w;
editor e;
w.begin(300,200,1300,700,"Modification");
w.sizerange(10,20,0,0);
e.create(200,220,1000,200,"Editor");
e.addstyle(m);
e.value("This
is an interesting style");
//We use the style of key C on interesting
e.setstyle(10,22,'C');
e.annotate(“a”,
‘E’); //each a is
assigned the E style
w.end();
w.run();
It is actually possible to redefine a
style for a given editor. The function addstyle
must be called again.
//A map describing the styles
available within the editor
map m={'#':[FL_BLACK,FL_COURIER,FL_NORMAL_SIZE
],
'truc':[ FL_DARK_RED,FL_COURIER,FL_NORMAL_SIZE ]};
//we modify one item in our
map... We keep the same key…
//The section in the text based
on ‘truc’ will be all modified…
function test(button b, editor e) {
m["truc"]=[
FL_DARK_GREEN,FL_COURIER,FL_NORMAL_SIZE ];
e.addstyle(m);
}
window w;
editor e;
button b(e) with test;
w.begin(300,200,1300,700,"Modification");
w.sizerange(10,20,0,0);
e.create(200,220,1000,200,"Editor");
e.addstyle(m);
e.value("This
is an interesting style");
e.setstyle(10,22,'truc');
b.create(1230,20,30,30,FL_Regular,FL_NORMAL_BUTTON,"Ok");
w.end();
w.run();
It is also possible to associate a
style with a specific message. This message will be displayed when the mouse
will hover above an element having that style. The only modification necessary
is to add one or two more elements to each item from the style description.
A style description is composed of: [itemcolor,font,fontsize].
We can add a message to that item: [itemcolor,font,fontsize,”Message”].
And even a color which will be used as
a background color for that message:
[itemcolor,font,fontsize,”Message”,backgroundcolor].
If the background color is not
provided, then the defined color itemcolor
from the style will be used.
map m={'#':[FL_BLACK,FL_COURIER,FL_NORMAL_SIZE
],
'truc':[ FL_DARK_RED,FL_COURIER,FL_NORMAL_SIZE,
”THIS IS A TRUC”,FL_YELLOW]};
When the mouse will hover above a piece
of text with the style truc, it will
display a yellow box with the message: THIS
IS A TRUC.
The callback must have the following
signature
function vhscroll(editor e, object n);
function mouse_callback(editor e, map coords, object
n);
The second parameter is a map with the
following keys:
coords[“button”] the
value of the last button that was pushed (1,2 or 3)
coords[“x”] the
X coordinate within the window of the mouse
coords[“y”] the
Y coordinate within the window of the mouse
coords[“xroot”] the
mouse absolute X coordinate
coords[“yroot”] the
mouse absolute Y coordinate
coords[“wheelx”] the
mouse wheel increment on X
coords[“wheely”] the
mouse wheel increment on Y
coords[“cursor”] the
mouse cursor position within the editor as a character position
function key(editor e, string k, int ikey object n);
In this
example, we set three different callbacks with the vertical scrolling, the
mouse and the keyboard. Each manipulation will update the line number in an
output field.
function cvscroll(editor e,woutput num) {
num.value(e.line());
}
function cmouse(editor e,map coords,woutput
num) {
num.value(e.line());
}
function ckey(editor e, string k, int
i,woutput num) {
num.value(e.line());
}
window w;
editor e;
woutput num;
w.begin(300,200,1300,700,"Window");
w.sizerange(10,20,0,0);
num.create(100,100,30,40,”Line”);
e.create(200,220,1000,200,"Editor");
e.onmouse(FL_RELEASE,cmouse,num);
e.onvscroll(cvscroll,num);
e.onkey(FL_KEYUP,ckey,num);
w.end();
w.run();
The following example shows how to
display on words with a specific style in your editor a little sticky note.
//A map
describing the styles available within the editor
map m={'#':[FL_BLACK,FL_COURIER,FL_NORMAL_SIZE ],
'movement':[
FL_RED,FL_COURIER,FL_NORMAL_SIZE ]};
//We define
the words that we want to recognize in the text
vector mvt=["move","run","stride","walk","drive"];
//whenever
the text is modified, we check for our above words
function
modified(editor e,int pos, int ninserted,int
ndeleted,int restyled,string del,self obj) {
//we unmark everything first
e.setstyle(0,e.size(),"#");
//then, we mark all our
movement words
e.annotate(mvt,"movement");
}
//we need
our message window to be displayed at the precise location of our mouse
window wmessage;
//This
method is called whenever the mouse cursor is on a non default style
function infostyle(int x,int y,map
sz,string style) {
//If there is already a sticky
note we do nothing
if (wmessage!=null)
return;
//we create a borderless
window, with a yellow background
//sz contains the string dimension
in pixels
wmessage.begin(x,y,sz["w"]+20,sz["h"]+20,style);
wmessage.backgroundcolor(FL_YELLOW);
wmessage.border(false);
box b;
//we display our style, which
is a string
b.create(5,5,sz["w"]+5,sz["h"]+5,style);
wmessage.end();
}
//This
function is called when the mouse is moved in the editor
function vmouse(editor
e,map infos,self n) {
//we get the style at the
cursor position which matches a position in the text
string style=e.getstyle(infos["cursor"],infos["cursor"]+1);
//If it is not a standard style
if (style!='#' && style!="")
//we create our
sticky note
infostyle(infos["xroot"],infos["yroot"],e.textsize(style),style);
else
//if it is the
standard style or no style at all, we close our window...
if
(wmessage!=null)
wmessage.close();
}
window w;
editor e with modified;
//we create
our window
w.begin(300,200,1300,700,"Marking movement words");
w.sizerange(10,20,0,0);
//then our
editor
e.create(200,220,1000,200,"Editor");
//we add
the style
e.addstyle(m);
//and we
also need a mouse callback
e.onmouse(vmouse,null);
w.end();
w.run();
It is possible
to define a scrolling region within a window. The type scroll can be used at
this effect.
It exposes the
following methods:
1.
create(int
x,int y,int w,int h,string label): Create a scrolling
region
2.
resize(object):
make the object resizable
kifltk offers
two sorts of slider. One of these sliders displays a value with the slide bar
itself.
The slider must
be attached with a callback function in order to catch any modifications. The
function must have the following signature:
function callback_slider(slider s,myobj obj) {
//the slider value is returned with value()
println(s.value());
}
slider s(obj) with callback_slider;
The slider
exposes the following methods:
1.
align(int align): define the slider alignement
2.
bounds(int x,int y): defines the slider
boundaries
3.
create(int x,int y,int w,int h,int align,bool
valueslider,string label): Create a slider or a valueslider (see below for a list of alignment
values)
4.
resize(object): make the object resizable
5.
step(int): define the slider step
6.
type(int x): Value slider type (see below for the list of
slider types)
7.
value():return the slider value
8.
value(int i): define the initial value for the slider
FL_VERT_SLIDER
FL_HOR_SLIDER
FL_VERT_FILL_SLIDER
FL_HOR_FILL_SLIDER
FL_VERT_NICE_SLIDER
FL_HOR_NICE_SLIDER
This example
shows how a slider can control the movement of a rectangle in another window.
//A small
frame to record our data
frame mycoord {
int color;
int x,y;
function _initial() {
color=FL_RED;
x=0;
y=0;
}
}
//we
declare our object, which will record our data
mycoord coords;
//we
declare our window together with its associated drawing function and the object
coord
window wnd(coords) with
display;
//We cheat
a little bit as we use the global variable wnd to
//access
our window…
function
slidercall(slider s,mycoord o) {
//we
position our window X according to the slider value
o.x=s.value();
wnd.redraw();
}
slider vs(coords) with
slidercall;
//We need
to instanciate the mouse callback
wnd.begin(100,100,300,300,"Drawing");
wnd.sizerange(10,10,0,0);
//we create
our value slider
vs.create(10,10,180,20,FL_ALIGN_LEFT,true,"Position");
//the values
will be between 0 and 300
vs.bounds(0,300);
//with the
initial value 100
vs.value(100);
wnd.end();
wnd.run();
The object tabs exposes everything that is
necessary to create tabs in a window. This object is associated with the object
group, which is used to group widgets together in a single
block.
The tabs
object exposes the following methods:
1. add(wgroup g): dynamically add a new tab.
2. begin(int x,int y,int w, int h,string
title): Create
a tab window and begin initialization
3. current(): return the current active tab
4. current(wgroup t):
activate this tab
5. end(): end the tabs construction
6. remove(wgroup g): remove the group g from the tabs
The group
object exposes the following methods:
1. activate(): activate the tab
2. begin(int x,int y,int w, int h,string
title): Create
a widget group and begin initialization
3. end(): end the group construction
·
The creation of a tabs section is quite simple. You create a tabs box, in which all the different elements will be stowed.
·
For each tab, you need to create a
specific widget group.
·
The dimension of a group should be inferior
in height to the original tabs box.
·
Each
group should have the same dimension.
·
The
second group should always be hidden.
It is also
possible to associate a group with a callback as with window. When a group is declared with an associate callback, this
callback is called each time the window must be redrawn. See window for more information. Most of the
functions available for drawing in the object window are also available for wgroup.
In this
example, we build a simple window with two tabs.
window wnd;
//We create
our main window
wnd.begin(100,100,500,500,"TABS");
//then a
tab section
wtabs tabs;
//which we
define as a box
tabs.begin(10,55,300,325,"Onglets");
//the first
group is a list of widget
wgroup g1;
//we begin
loading our widget
//The size
is 80=55+25 and the height is 300=325-25
g1.begin(10,80,300,300,"Label&1");
//there
will be only one
winput i1;
i1.create(60,90,240,40,true,"Input 1");
//our group
1 is now finished
g1.end();
//then we
create our second tab section as a group again
wgroup g2;
//the size
of this group is exactly the same as g1
g2.begin(10,80,300,300,"Label&2");
//IMPORTANT:
we hide it
g2.hide();
//We add
our new widgets
winput i2;
i2.create(60,90,240,40,true,"Input 2");
//our group
2 is now finished
g2.end();
//so are
our tabs
tabs.end();
wnd.end();
wnd.run();
In this new
example, we show how tabs can be dynamically added to an existing tab window.
We implement a button, which when pressed, triggers the creation of a new tab.
A second button shows how a tab can be removed from the list of tabs.
int nb=0;
//This
function will delete the current active tab
function
removetab(button b, wtabs t) {
//we get the current active tab
in a self since we do not want to have
//a copy of that element but
the actual pointer to this element
self x=t.current();
t.remove(x);
}
//this
function creates a new tab which is appended to the existing tab structure
function addtab(button
b,wtabs x) {
wgroup g;
//same size fits all
g.begin(10,80,300,300,"Label&"+nb);
//IMPORTANT: we hide it unless
it is the first one
if (nb!=0)
g.hide();
//We add our new widgets
winput i;
i.create(60,90,240,40,true,"Input "+nb);
//our group is now finished
g.end();
nb++;
//we add it to our existing tab
structure...
x.add(g);
}
window wnd;
//We create
our main window
wnd.begin(100,100,500,500,"TABS");
//then a
tab section
wtabs tabs;
//which we
define as a box
tabs.begin(10,55,300,325,"Onglets");
//The tabs
section is of course empty
tabs.end();
//we add a
button to trigger the creation of a new tab...
button b(tabs) with
addtab;
b.create(400,100,50,30,FL_Regular,FL_NORMAL_BUTTON,"Add");
//This
button will delete the current tab
button br(tabs) with
removetab;
br.create(400,140,50,30,FL_Regular,FL_NORMAL_BUTTON,"Remove");
wnd.end();
wnd.run();
//Our
redrawn function
function drawing(wgroup
w,self n) {
w.drawcolor(FL_BLACK);
w.circle(100,100,100);
}
//we create
a group that is linked with a redrawn function
wgroup fen with drawing;
fen.begin(10,80,300,300,"Infos");
fen.end();
//We then
associate it as a tab
tabs.add(fen);
This object is
used to display a window to browse your disks to fetch a file or a directory.
The object can be created with a callback function, whose signature is the
following:
function callback_filebrowser(filebrowser
f,myobject object);
However, if you
do not declare any callback function, the function create returns the selected file pathname.
It exposes the
following methods.
1.
close(): Close the file browser
2.
create(string intialdirectory,string filter,int method,string
label): Open a file browser, according to method (see below). If no
callback function is declared, then it returns the selected pathname.
3.
ok(): return true if ok was pressed
4. value(): Return the selected
file
There are
different ways to open a filebrowser, each with a different action. The
possible values are the following:
·
FL_DIR_SINGLE:
to open a single file at a time
·
Fl_DIR_MULTI:
to open multiple files at a time
·
FL_DIR_CREATE:
to create a new file
·
FL_DIR_DIRECTORY:
to select a directory
//we check
wether the element was chosen
//then we
close our window
function
choose(filebrowser f,self b) {
if (f.ok()) {
println("Ok:",f.value());
b=true;
f.close();
}
}
bool b=false;
filebrowser fb(b) with choose;
fb.create('C:\XIP',"*",
FL_DIR_SINGLE,"Choose your file");
A simpler
solution is:
filebrowser f;
string value=f.create(".","*.*",FL_DIR_SINGLE,"Open");
println("Value:",value);
KiF also provides a way to play “wav” and “mp3” files.
You simply load a file and you can play it anywhere in your code. It provides
at this effect the type: “sound”.
The API
exposes the following methods:
1.
load(string
pathname): Load
the sound pathname.
2.
play():
play
the sound.
3.
stop():
stop
the sound.
You can also load a sound when you
create a sound object...
sound s;
s.load('C:\XIP\XIP7\sound\Kalimba.mp3');
s.play();
You can also load a sound with the
following declaration:
sound s('C:\XIP\XIP7\sound\Kalimba.mp3');
The curl type is used to load HTML page from
the internet. It is based on the cURL (http://curl.haxx.se/)
library and offers some basic tools to handle HTML pages.
This library should be loaded with: kifcurl as name.
1.
password(string
user,string psswrd): to
provide a site with a user and a password
2.
proxy(string
proxy): to
set a proxy connection
3.
url(string
html): to load a url
4.
url(string
html,string filename):
to load a url and store the result in a file.
There are two different ways to load an
HTML page: either through a callback function or with a filename.
The first
possibility is to associate your url
object with a callback function, whose signature should be the following:
function url_callback(string
content,myobject o);
The function
will be associated with the following declaration:
url u(o) with url_callback.
In that case,
you should use: url(string html) as method in order to have each block of texts
loaded from your web page. For each block, your url_callback will be called with the block content as value.
use('kifcurl');
function fonc(string
content,self o) {
println(content);
}
curl c with fonc;
//we set a
proxy, which will be used as a way to load your web pages through
c.proxy("http://myproxy.mycompany:5050");
//we load
our web page. For each block, func will be called...
c.url("http://www.liberation.fr/");
The other
possibility is to provide the url
method with a filename, which will be used to store the content of your web
page. In that case, do not declare your variable with a callback function.
use('kifcurl');
curl c;
//we set a
proxy, which will be used as a way to load your web pages through
c.proxy("http://myproxy.mycompany:5050");
//we load
our web page. For each block, func will be called...
c.url("http://www.liberation.fr/","c:\temp\myfile.html");
The kifpython library
provides the necessary methods to execute some Python code in KiF. It exposes a
new type: python.
The base KiF types: boolean, int, long, float, fraction, string, vector containers and map
containers are automatically mapped onto the corresponding Python types.
The python type
exposes the following methods:
1. close():
Close the current Python session.
2. execute(string
funcname,p1,p2...): execute a python function
with p1,p2 as parameters
3. import(string
python): import a python file
4. run(string
code): Execute python code
5. setpath(string
path1,string path2 etc...): Add system paths to python
The setpath
method is crucial to use the import
method, which works exactly as the import
keyword in Python. If you want to import a Python program at a specific location,
which has not been referenced through PYTHONPATH, you need to add it with setpath first.
First we implement a small Python program, which we
call: testpy.py
val="here"
#The input variables are automatically
translated from KiF into Python variables
def Iteste(s,v):
v.append(s)
v.append(val)
return v
Then we implement our own KiF program, which will call
this file (which we suppose to be in the same directory as our KiF program)
//We need to use kifpython for our own
sake
use("kifpython");
//we need a variable to handle the Python
handling
python p;
//we suppose that our Python program is
in the same directory as our KiF program
p.setpath(_paths[1]);
//We then import our program
p.import("testpy");
vector v;
string s="kkk";
//We execute the Python function Itest, which takes as input a string and
a vector,
//which will converted into Python
objects on the fly.
//The output is automatically re-converted
into a KiF vector (from the Python vector)
vector vv=p.execute("Itest",s,v);
println(vv); //output is: [‘kkk’,’here’]
p.close(); //we close the session
The WordNet 3.0 library has been embedded into a KiF
library. It can be called from any program with the command: use(“kifwordnet”).
This library exposes the following methods:
6. findinfo(string
word,string pos,string type,string senses,int definitions): Retrieve all senses of a word according to the different flags (see
below for a list of type, senses and pos flags)
definition
can have the following values:
0
is no definition and no list of POS.
1
adds the list of POS
2
adds the definitions.
3
adds all.
7. findsynset(int
synset,string pos,int definition,string word): Retrieve
all senses associated with a synset. 'word' is optional.
definition
can have the following values:
0
is no definition and no list of POS.
1
adds the list of POS
2
adds the definitions.
3
adds all.
8. frequency(string
word,string pos):
Return the 'frequency' of a word as a
value between 0..7, 0 being the less frequent
The list
of part of speeches is the following:
ALL_POS,NOUN,VERB,ADV,ADJ,ADJSAT
The list
of types, which can be used within a findinfo
method is the following:
ANTPTR, HYPERPTR, HYPOPTR,ENTAILPTR, SIMPTR, ISMEMBERPTR, ISSTUFFPTR,
ISPARTPTR, HASMEMBERPTR, HASSTUFFPTR, HASPARTPTR, MERONYM, HOLONYM, CAUSETO, PPLPTR,
SEEALSOPTR, PERTPTR, ATTRIBUTE, VERBGROUP, DERIVATION, CLASSIFICATION, CLASS
If you want more information about these
flags, please refer to WordNet documentations.
ALLSENSES, SYNS, FRAMES, COORDS, RELATIVES, HMERONYM,
HHOLONYM, WNGREP, OVERVIEW, CLASSIF_START, CLASSIF_USAGE, CLASSIF_REGIONAL,
CLASS_START, CLASS_USAGE, CLASS_REGIONAL, INSTANCE, INSTANCES
If you want more information about these
flags, please refer to WordNet documentations.
use("kifwordnet");
map
res;
res=kifwordnet.findinfo("drink","NOUN","HYPERPTR","ALLSENSES",1);
println(res);
The
result is the following:
{
'synset':7885223,
'words':['drink'],
'pos':’n’,
'nextsynset':{
'synset':748515,
'words':['drink','drinking','boozing','drunkenness','crapulence'],
'pos':’n’,
'nextsynset':{
'synset':7881800,
'words':['beverage','drink','drinkable','potable'],
'pos':’n’,
'nextsynset':{
'synset':9270508,
'words':['drink'],
'pos':’n’,
'nextsynset':{
'synset':839778,
'words':['swallow','drink','deglutition'],
'pos':’n’,
'whichword':2,
'synsets':[838098,1170052,1201856,840057,840189,843494]
},
'whichword':1,
'synsets':[9225146,7075172]
},
'whichword':2,
'synsets':[21265,
14940386, 797113,1170052, 7844042, 7882420,7883251, 7884567, 7890970, 7891095, 7891189,
7891309, 7913180, 7914006, 7914128, 7914271, 7919310, 7921455, 7922764, 7924033,
7925966, 7926785, 7927197, 7929519, 7933274, 7933530, 7936263]
},
'whichword':1,
'synsets':[748011,10537,10385,10537,1172275,1171183,1172275,1171183,748834],
},
'whichword':1,
'synsets':[7578363,1170052,7883860,7883980,7884413,7885937,7912499,7912619,7912726,7912834,7912933,7913081,7916773,7916872,7916970,7918454,7918601,7923034,7923297,7923665]
}
The result is a complex map, with the
following keys:
·
synset is the synset of the current
element (see wordnet documentations for more details)
·
synsets is a list of other synsets
matching the current word.
·
pos is the current part of speech
·
ppos is a list of part of speech
matching synsets, when the option definition=1 or definition=3 is set.
·
nextsynsets is another possible
instantiation for the current word.
·
whichword is the current position of
the word in the synsets list.
·
words is the list of word strings
matching the synsets list.
·
definition is a definition given for the
current word, is definition is set to
2 or 3.
It is possible
to execute a KiF program through a C++ API.
KiF provides
four functions to this effect, which should be used in the following way:
This instruction loads a list of KiF
programs in the same memory space, in a similar fashion as “–kif”, on the
command line.
This function
returns a handler on the KiF program
which has been loaded.
This function runs a specific KiF
function, which returns as a result a string.
KiF provides a specific function: use, which is used to load an external
compatible library into a KiF program in order to enrich the language with new
types and new capabilities. The use
function must be placed at the beginning of a program before any other
definitions.
use('kifsqlite');
sqlite mydb;
mydb.open('C:\XIP\Test\SampleLogGalateas\test.db');
It should be noted that external KiF
libraries are compiled as either DLL or dynamic libraries according to the
platform and thus obeys the way DLL or dynamic libraries are found and loaded.
Hence, on Unix, dynamic libraries are
found according to the variable content: LD_LIBRARY_PATH.
In another embodiment, this function
can take two parameters. The first one is the platform and the second one the path
itself. Kif recognizes three different platforms: WINDOWS, MACOS, UNIX and UNIX64, which should be provided as
strings.
// this library will only be
loaded on UNIX platforms.
use('UNIX','/usr/lib/kifsqlite');
// this library will only be
loaded on WINDOWS platforms.
use('WINDOWS','c:\dlls\kifsqlite');
// this library will only be
loaded on MACOS platforms.
use('MACOS','/usr/lib/kifsqlite');
sqlite mydb;
mydb.open('C:\XIP\Test\SampleLogGalateas\test.db');
Use can also be used with three
parameters, the two first parameters being tested one against the other. The
first parameter should be a _args
variable, whose value will be compared against the next parameter.
// this library will only be
loaded if the value of _args[0] is “TOTO”
use(_args[0],'TOTO','/usr/lib/kifsqlite');
KiF provides a specific C++ template to
implement a KiF compatible library. It is not a template in the C++ sense, but rather a C++ file implementing
anything that is necessary to add new capabilities to KiF.
The first operation consists of
duplicating the file: kiftemplate.cxx
into a new file.
In this copy, you will replace the
keyword: TemplateName with your own
new name. This replacement is done in any editor with the replace command.
Replace every single instance of TemplateName
with your new name.
For the sake of the description, we
will replace TemplateName with the
keyword Try.
The new file exposes two classes: KifIteratorTry and KifTry.
This method is the heart of your
library. This is the first function that will be called once your library has
been loaded.
Once you have replaced every single
instance of TemplateName in your file
with the keyword Try, you will
discover in this specific function the following line:
Try_type=kifcode->KifAddNewType("Try",CreatekifTryElement);
Try_type
is a KifType variable, which receives as value the new identifier that will be
associated to your new object. The first parameter of KifAddNewType is a string,
which will be the type that will be used in your KiF program.
Try toto;
1.
CreatekifTryElement
is the function that the KiF compiler will called when it finds a Try type in a KiF program. This
function, which is also implemented in your new file, creates a new object of
that new type for the compiler.
2.
MethodInitialisation(“sample”,&KifTry::MethodSample,NBARGS,”Description”);
this method is the most important one. It is the one that defines a new method
that will be exposed through your new object. It needs a name (a simple string)
and a KifTry method implementation. NBARGS is a combination of one or many
predefined values: P_NONE,P_ONE, P_TWO (see kifbase.h for the whole set) etc…
Add as many MethodInitialisation as you wish. Do not forget to implement the function
itself.
The name of
this method, which is the entry point of the library is based on the library name, together with the keyword:
KifInitialisationModule.
It is then important to keep the name of your library in par with your template name, otherwise, KiF will not
be able to find this entry point.
If your library name is mylib
then this method must be called:
KiF implements specific garbage
collectors for the following objects, namely:
·
KifString
·
KifInteger,
·
KifFloat,
For
these objects, you MUST NOT USE “new
KifString” or “new KifFloat”, but
the following methods, which are exposed in KifCode (see kifbase.h):
Exported KifString*
Providestring(string& z);
Exported KifString*
Providestringraw(string z);
Exported KifInteger* Provideinteger(long val);
Exported KifFloat* Providefloat(double val);
KiF, for efficiency reasons, maintains lists of
predefined objects, which it actually reuses during execution. Thus, KiF has a
list of integers, floats or strings from which it extracts the right objects
when needed. If you need to return a string
object, then call:
kifcode->ProvideString(value).
This class is the main class of your
new library. It implements the behavior of your library. It derives from KifObject and it expects some methods
and some new variables. As a C++ class, you need to declare your own variables,
and to instantiate their initial value in the class constructor.
Your new class will implement something
similar to the lines below.
class KifTry : public
KifBasic {
public:
//These two static objects are
used for internal method descriptions
static map<string,KifCallMethod>
kifexportedmethods;
static map<string,TryMethod> linkedmethods;
//----------------------------------------------------------------------
//This SECTION is for your
specific implementation...
//Your personal variables here...
string s; ß WE ADD our new variable here
//----------------------------------------------------------------------
KifTry(KifCode* kifcode,KifElement* base) :
KifBasic(kifcode,base,Try_type) {
s=””;
}
We have
modified the class by implementing a new field: s. You can add as many variables as you want.
If you want your new object to have
iterator capabilities, you may implement a class like this one.
·
You need to provide a Begin method, to initialize your
iteration. Begin returns the first
element of your list.
·
You need to provide a Next method, to go one step further.
This method should return the next element of your list.
·
You need to provide a End method, which return kifTRUE is the end of the list is
reached, kifFALSE otherwise.
·
You need to provide a IteratorKey, which returns the key of
the current element.
·
You need to provide a IteratorValue, which returns the value
of the current element.
Furthermore, an iterator provides a reverse
Boolean variable to indicate the direction of iteration, which might be of some
use if you want to provide a reverse iterator to your object.
Newiterator
is the method which is called when an iterator is instantiated. If you do not
need any iterator, you can remove both this method and KifIterator class. If you need an iterator, then this method is the
right one to initialize your iteration.
When an KiF finds an instantiation in a
program, this is the method that is always called.
Try toto;
toto="abcdef";
The Setvalue
method will be called if a program contains a line like the one above.
bool Setvalue(KifElement* kval,KifElement*
kindex,KifElement* domain) {
s=kval->String();
return true;
}
Setvalue
receives three parameters:
If we have the
following line:
toto[10]="abcdef";
Then kindex
will have 10 as a value.
You can provide a copy of your object:
KifElement* Copy(KifDomain* kp,KifElement*
dom=kifNULL) {
KifTry*
kperso=new KifTry(KifCurrent(),NULL);
//We initialize kperso with our current
value
kperso->s=s;
return kperso;
}
If you do not
want to provide any copy, you can then return this.
These methods are called for you
whenever a Try object will be in a
string, integer, float or Boolean context.
string String() {return
s;}
If you want to provide your class with
an interpretation of the following operators: +,-,*,/,%,^,&,|,<<,>> then you might want to
overload these methods.
KifElement* plus(KifElement* a,KifElement* b)
{
string
s1=a->String();
string
s2=b->String();
s1+=s2;
return
kifcode->Providestring(s1);
}
If you want to compare Try object together, then you might
consider overloading these methods:
KifElement* same(KifElement* a) {
if (s==a->String())
return kifTRUE;
return kifFALSE;
}
The element a is compared with this.
There is slight difference between
Clean and Clear. Clean is called when an element is deleted from the garbage
collector. Clear is called when an element is simply reset to its initial
value. Hence, the “reference--” in
Clean.
Of course, the goal of such a library
is to implement your own code. A new method should be implemented in two steps.
First, call MethodInitialization with the name you have chosen for your new
method and the method that should be called in that case.
MethodInitialization("command",&KifSys::MethodCommand,P_ONE,”command(string s): Execute a command”);
Second, implement your MethodCommand in KifTry.
KifElement* MethodCommand(KifElement*
contextualpattern,
KifDomain* domain,
KifCallFunction*
callfunc) {
if (callfunc->Size()!=1) //Not really necessary, as
you have constrained to only one parameter your method with P_ONE, but it could
be important
kifcode->Returnerror("MYKIF(800): Missing parameter");
//0 is the first parameter and so on...
KifElement*
kcmd=callfunc->Evaluate(0,domain);
if
(contextualpattern->type==kifString)
s=kcmd->String();
//you may return any value of course...
return kifTRUE;
}
A method
implemented in this way requires three parameters:
KifElement*
contextualpattern; This variable is a bit
difficult to understand. It stores the context in which the action is taking
place. For instance, if this method is called through a string initialization,
then contextualpattern type will be kifString as in the example below:
string str=toto.command("my string");
str in this context imposes a String context to the whole formula, which is
reflected in the value of contextualpattern (which in this case will point to str KiF representation).
KifDomain*
domain; this variable defines the frame
in which the action is taking place.
KifCallFunction*
callfunc; this variables points to the
function call. It enables the access to the parameters.
A
KifCallFunction exposes two main methods:
A method should
always return a KifElement of any sorts as output.
The name MethodCommand is only for the sake of
the demonstration. You can choose whatever name your want as long as the link
between its name and the method itself is implemented through a call to MethodInitialization.
The main
difference between implementing a frame version of your library with the
previous one relies on the use of kifframetemplate.cxx
as a source for your own derivation and the use of two specific methods to
handle frame variables (field hereafter).
First of all, as for kiftemplace.cxx, replace the keyword: FrameTemplate with your own new name.
You will notice some difference with
the previous version. First difference, the basic class derives from KifFrame, which gives access to some new
specific methods.
Second, the presence of the variable: localDefinition, which is used to share
your specific frame implementation.
As for the other definition, you only
need to define your own frame variables
or fields and your own methods, with
two major differences.
If you want to enlarge your new frame with specific fields, you need to declare them through a call to Newfield in your class constructor. You
will find an example of such a declaration in the kifframetemplate.cxx file:
KifFrameTemplate(KifCode*
kcode,KifElement* base,string& name) : KifFrame(kcode,base,name) {
//Mandatory
to update the new methods exposed by this frame
map<string,KifCallMethod>::iterator it;
for
(it=kifexportedmethods.begin();it!=kifexportedmethods.end();it++)
declarations[it->first]=&it->second;
//We create
our own fields.
//For the
sake of our example an integer (i) and a string (s).
Newfield("i",kcode->Provideinteger(10));
Newfield("s",kcode->Providestringraw(“here”));
}
The first
parameter is the name of your new field
in your frame implementation and the
second one is an object, which defines the type of that new object. It might be
anything such as: KifFloat, KifBoolean,
KifTime, KifMap or KifVector.
This is similar
to the following frame declaration:
frame FrameTemplate {
int i=10;
string
s="here";
}
If you implement your own methods, they
will need access to the fields of the
current frame object, which Getfield will do for you.
KifElement*
vi=Getfield("i",domain);
KifElement*
vs=Getfield("s",domain);
For instance, in the sample method which has been declared in
kifframetemplate.cxx, you will see
the two above lines, which will return the actual variables attached to these
two field names.
In a non-frame implementation, the
method String, Integer, Float, Boolean,
Size must be provided by the user. However, in a frame, the problem is a bit different. No String or Integer can be
implemented directly. As for a frame,
where the user must provide the right method to evaluate it as a string or as
an integer, in the similar way, a string
method should be provided by the programmer in the same way as other functions.
MethodInitialization("string",&KifFrameTemplate::MethodString);
In order to
implement a string method, the
programmer must provide a MethodString associated
to the keyword: string.
An example of
such a method is available in kifframetemplate.cxx.
KifElement*
MethodString(KifElement* contextualpattern,
KifDomain* domain,
KifCallFunction* callfunc) {
//No
parameter
if (callfunc->Size()!=0)
kifcode->Returnerror("KIF(800):
Wrong number of parameters");
KifElement* vs=Getfield("s",domain);
return vs;
}
kifframetemplate.cxx
contains the code of a declaration equivalent to the following one:
frame FrameTemplate {
int i=10;
string
s="here";
function
sample(int xi,string xs) {
i+=xi;
s+=xs;
return(true);
}
function
string() {
return(s);
}
}
The next pages
describe the specific integration of KiF within the Xerox Incremental Parser (XIP). The following instructions can only
be accessed through XIP with the API, through the “-kif” and “-kifargs”
instructions on the XIP command line and of course through the grammars.
There are different ways of passing
arguments to a KiF program within XIP. The easiest way is to use –kifargs on the command line.–kifargs should be the last command in
the list of commands as all the strings after this keyword will be passed as an
argument to your KiF program.
xip –kif mykif.kif –kifargs first second third
The other way to pass arguments is to
use a “kifarg:” field in a GRM file,
the content of the field is then passed as an argument to the program. More
than one “kifarg:” can be declared at
a time.
If you
instantiate arguments with both “-kifargs”
and “kifarg:”, then it should be
noted that the arguments from the GRM file will be stored before the ones on the command line.
Each KiF object is associated with a
reference, which is either incremented or decremented according to its use.
When a KiF function is called from a XIP rule, then the garbage collector is
automatically called and all objects created during the execution of that
function are cleaned and removed from memory. However, the garbage collector
can also be called whenever a specific threshold is crossed. By default, the
threshold value, which is computed as a number of KiF objects created, is set
to 10.000. However, it is possible to reset this value from the command line
with -kifsize and provides a
different threshold value.
xip –kifsize 100000 etc…
KiF also exports a specific function, which should be placed
at the beginning of the code to set the garbage size value.
The XIP API also exports a specific
method: KifSetSize, which takes as
input the new threshold.
KifSetSize(int threshold);
In this case, whenever the number of
KiF objects created is superior to this threshold,
the garbage collector is called and memory is freed from unused objects.
A KiF function can be called from any XIP rule, anytime. The
only constraint is that the KiF function must be declared before its
utilization in a XIP grammar. Furthermore, any XIP variable can be used in a
KiF function. However, the reverse is not true. A KiF variable is not visible
from a XIP rule. Finally, a KiF section is a XIP file should always be the last section in that file.
//File: test.kif
Variables:
string s="in
XIP";
KiF:
function display() {
print("S=",s,"\n");
}
//File: script.xip
Script:
display();
Run
S="in
XIP"
A KiF function can modify any XIP
variable. It can only return numerical
values.
Variables:
string s="in
XIP";
int i;
KiF:
function display() {
print("S=",s,endl);
s="value from KIF";
return(2);
}
Script:
i=display();
print("S
again:"+s+":"+i+"\n");
S=in XIP
S again: value from KIF:2
XIP objects,
such as syntactic nodes, dependencies or generation nodes, can be passed to a
KiF function in a transparent manner.
KiF:
function display(node n) {
print("NODE POS="+n+"\n");
}
Script:
|Noun#1| {
display(#1);
}
RUN
NODE POS: NOUN
KiF:
function display(dependency d) {
print("Dependency="+d+"\n");
}
Script:
|Noun#1|
if
(subj$1(#2,#1)) {
display($1);
}
RUN
Dependency=SUBJ
It is also possible to use KiF function
in a if or in a where. The function should return then a numerical value, where false is 0 and true is anything else.
KiF:
function testpos(node n) {
//We need to force the STRING
interpretation of n…
if ("noun"==n)
return(true);
return(false);
}
Script:
|Noun#1|
if (testpos(#1))
DEP(#1);
If KiF is
case-sensitive, XIP is not. This might create some issues as for XIP the two
functions Display and display will be a single function. If
you implement KiF functions that might be called from a XIP rule, avoid playing
on uppercase or lowercase characters to distinguish between different
functions.
The following C++ instruction describes how KiF programs can
be loaded or executed from within the XIP API.
This instruction loads a list of KiF
programs in the same memory space, in a similar fashion as “–kif”, on the
command line. However, in this case, the KiF programs are loaded within an
empty XIP parser object, which enables functionalities such as FST compiling or
Callback functions. Furthermore, the initialization of a XIP object also
enables the passing of XIP specific parameters to the KiF programs.
This function
returns a handler on the
GlobalParseur object.
This function runs a specific KiF
function, which returns as a result a string. However, in this case, we use the
handler from the XipKifLoad
instruction.
If a KiF
program loads a grammar through a parser
object, it is possible through the API to catch on the fly the different
analyses which are being produced.
In this case,
the programmer must first load the KiF program through a XipKifLoad instruction, in order to set a specific callback
function through a XipSetCallBack.
The execution should then be done through
a XipKifExecute instruction.
C++ program
void mycall(int
ipar, XipResult* xip,void* data) {…}
//First we load our KiF program
vector<string> paths;
paths.push_back(‘C:\Test\parse.kif’);
vector<string> arguments;
arguments.push_back("C:\Test\French\french.grm");
int ipar=XipKifLoad(paths,arguments,false);
//Then we set our callback
function
XipSetCallBack(ipar,mycall,NULL);
ostringstream os;
arguments.clear();
arguments.push_back("La
dame mange une glace.");
//We can then execute the
function that is exported by our KiF program
string kifres=XipKifExecute(ipar,"parsing",arguments,&os,false);
KiF program: parse.kif
parser p;
//we load our grammar from within
KiF
//args[1] corresponds to: C:\Test\French\french.grm
p.load(args[0]);
//s=La dame mange une glace.
thread parsing(string s) {
string
r;
//we parse
our function
r=p.parse(s);
//kifres
//we parse our function
//will
receive r as a result (converted as a string).
return(r);
}
The callback
function: mycall will be
automatically passed to the grammar french.grm,
which will then enable programmers to have access to the XipResult objects
generated for that specific parse.
When a KiF
function is called from a XIP rule, the node parameters #1,#2 etc. can be
passed to that KiF function as node.
1. child(): return the
first child node under current node
2. feature(string att):
return the value of an attribute
3. feature(string att,string val): test the value
of an attribute
4. features(): Return a map a results, in which attributes as stored as keys, and
values as...values
5. features(smap m): Store the values in the map
in the XIP node (or dependency), where the maps keys should be the attributes.
6. last(): return the last
child node under current node
7. leftoffset(): return the left offset
8. leftoffsetchar(): return the left character offset
9. lemma(): return the
lemma string. It should be noted, that if the recipient variable is
a vector and the element is ambiguous, then a vector of lemmas can be
returned as for pos above.
10. lemma(string s): replace the lemma of the current node
with s.
11. lemma(string lem,string pos,smap
feats): add
a new “reading” to a node. “lem” is the new lemma, “pos” is
the part of speech and “feats” a
feature structure.
12. name(): return the part
of speech
13. next(): return the next
node after current node. Can also return the next lexical node in the
sentence when the structure is flat.
14. sister(): return the next node. If the
structure is flat, then sister() returns null.
15. nodeinfos(): return the node infos value
16. nodeinfos(string v): set the node infos value
17. number(): return the ID
of current node
18. offset(int left,int right): left and right receive the offsets
19. offsetchar(int left,int right): left and right receive the character offsets
20. parent(): return the
parent node above current node
21. pos(): return the part
of speech. It should be noted, that if the recipient variable is a vector and
the element is ambiguous, then a vector of pos can be returned (the same
applied to name above).
22. previous(): return the previous node after current node
23. readings(): return a vector of all possible readings for a given word.
24. removefeature(string att): remove a feature from a
node.
25. rightoffset(): return the right offset
26. rightoffsetchar(): return the right character offset
27. righttokenoffset(): return the right token offset
28. rulenumber(): return the rule number, which applied
to create this node.
29. sentence():
return the string spanned by the node out
of the buffer.
30. setfeature(smap m):
feature assignment to the node from a map,
where the keys are XIP attributes.
31. setfeature(string att, string val): feature
assignment to the node
32. surface(): return the surface string
33. surface(string s): replace the surface string of the
current node with s.
34. tokenoffset(int left,int right): left and right receive the token offset
35. xmlnode(): return the XML node associated with this node (TOKENIZE mode)
Return the part of speech
Return the node id.
//XIP rule
|Noun#1| if
(testkif(#1))…
//KiF code
Function testkif(node n) {
print("N=",n,"\n"); //display: NOUN
map
features;
n.features(features);
vector v=n.lemma(); //more than one value can
be returned
return(true);
}
This type corresponds to the $1,$2 etc... dependency variables
in XIP.
1. feature(string att):
return the value of an attribute
2. feature(string att,string val): test the value
of an attribute
3. features(map feats): Features are stored in map as attribute/value
4. name(): return the
dependency name
5. parameters(): return a vector of node variable
6. pop(): remove the last
element on top of the dependency’s stack
7. pop(int i): remove the ith element from the dependency’s stack
8. push(string s): push s on the dependency’s stack
9. rulenumber(): return a vector of the rule numbers,
which applied to create this dependency.
10. setfeature(string att, string val): feature
assignment to the dependency
11. stack(vector v): return the dependency stack in a vector of strings
12. v=stack(): return the dependency stack in a vector of strings
Return the dependency name
Return the dependency id.
//XIP rule
|Noun#1| if
(subj$1(#2,#1) & testdep($1))…
//KiF code
Function testdep(dependency d) {
print("D=",D,"\n"); //display:
SUBJ
map
features;
d.features(features);
vector
v;
v=d.parameters();
return(true);
}
This object shares most of its methods with dependency.
However, it adds the following one:
1. child(): return the
first Generation Node child
2. last(): return the last child
3. next(): return the next
Generation Node
4. parent(): return the
parent Generation Node
5. previous(): return the previous Generation Node
6. rulenumber(): return a vector of the rule numbers,
which applied to create this generation node.
Return the generation name
Return the generation id.
//XIP rule
|Noun#1| if
(NP$#1(#2,#1) & testdep($#1))…
//KiF code
Function testdep(generation d) {
print("D=",D,"\n"); //display:
NP
map
features;
d.features(features);
return(true);
}
This object is used to handle XIP conceptual graphs, it
exposes many methods to project, extract or modify conceptual graphs. The
creation and initialisation of the graph must be done in the XIP grammar.
1. match(extractor): extractor should be a graph. We return a vector of all sub-graphs from the
current graph that match extractor.
2. name(): return
the name of the graph
3. pop(remove): remove should be a graph. We remove from the current graph, the sub-graphs
that match remove.
4. project(g): g
should be a graph. We project g on
the current graph.
5. replace(pattern,replacement): pattern and replacement should be two graphs. We replace the sub-graph that matches pattern
with a new replacement graph.
6. set(string name): give the graph a name
Graphs can be compared with the
operators ‘==’ and ‘!=’.
We return the name of the graph.
This type is very specific to XIP and is not available with
all version of the rule engine, depending on its license. With this type, it is
possible to load a finite-state transducer script and to apply it to any
string. It is also possible to create an FST on the fly.
1. add(string surface,string lemma): Store a new surface/lemma couple in the FST, which has been prepared with init().
2.
compact(int
option): Compact the current net: 1
is SMALLEST, 2 is SMALL, 3 is FAST, 4 is FASTEST.
6. down(string l, string feats): return the surface form of a lemma l with features
feats
7.
FST var(string
script): create and load an FST
script. Strateg is DEPTH strategy, flags is “”. UTF8 is false.
8.
FST var(string
script, boolean utf8): create and load an FST
script. Strategy is DEPTH strategy, flags is “”.
9.
FST var(string
script, string flags, int strategy,boolean utf8): create and load an FST script. Strategy=0 for a DEPTH strategy, 1
for a BREADTH strategy.
10. init(boolean utf8): Initialization of a net creation, step by step. If the
net should contain UTF8 characters, set utf8 to true.
11.
invert(): Invert the current net.
12. load(string script, string flags, int strategy,boolean
utf8): load an FST script. Strategy=0 for a
DEPTH strategy, 1 for a BREADTH strategy.
13.
loadregex(string
file,bool utf8): Load a file containing a
regular expression.
14.
loadspacedtext(string
filename,bool utf8): load a text file in which
words are stored according to the FST spaced-text format.
15. minimize(): minimize the arcs in the current net
16. negate(): return the negated automaton out of
the current automaton
17. optimize(): Optimize the arcs in the current net
18. save(string filename,boolean utf8): final operation after an init() and a series of add().
Store the final FST in filename.
19.
traverse(string pathname,
function f,imap grds,boolean lower): traverse a
FST with f(string
s,imap grds) called for each new token found in the FST of name pathname, on
the FST lower side if lower is true.
20.
uncompact(): Uncompact the current net.
21. unoptimize(): Unoptimize the arcs in the current net
22.
unvectorize(): Unvectorize the current net.
23. up(string w): return the vector of all readings for the word w
24. up(string w,int threshold,int flags): return the vector of all readings for the word w, with
a threshold and some edit distance flags, to detect words which are close to w
to an edit distance set of actions. The flags can have the following values:
a.
a_change: modification of characters
b.
a_delete: deletion of characters
c.
a_insert: insertion of characters
d.
a_last: compute the edit distance only for the last FST of a cascade
e.
a_prefix: check if a string is a prefix in the FST
f.
a_switch: switching two characters
next to each other
25.
vectorize(): Vectorize the current net.
Return the filename of the FST script
The following operators have been
mapped to specific automaton operations:
a1 | a2: return the union of two automata
a1 & a2: return the intersection of two automata
a1 + a2: return the concatenation of two automata
a1 - a2: return the difference between two automata
a1 * a2: return the composition of two automata
The result is always a fst object.
words["toto\t+Noun"]="titi";
words["titi\t+Noun"]="tutu";
myfst.compile(words);
res=myfst.up("titi");
print("Res=",res,"\n"); //display: [‘toto +Noun’]
//Another possibility is to
create the FST step by step...
fst myfstbis;
//First we prepare our FST
myfstbis.init();
iterator it=words;
//Then we add line after line:
surface,lemma
for (it.begin();it.nend();it.next())
myfstbis.add(it.value(),it.key());
//Eventually we save it
myfstbis.save("myfstbis.fst");
res=myfstbis.up("tutu");
print("Res=",res,"\n"); //display: [‘titi +Noun’]
ntm is a specific library based on the FST, which articulates
in one single step, morphological analyzis, normalization and tokenization.
ntm exposes the following methods:
1) loadntm(string
script,bool utf8): load a NTM script. The second
parameter is optional
2) lookupstring(string
sentence): We apply the ntm script onto a string. The
result is a vector of vectors.
3) lookupfile(string
filename): We apply the ntm script onto a file. The
result is a vector of vectors.
4) setsepconstraint(bool
v): We set the separator constraint in NTM.
5) setoptions(map
opt): We set the following options:
a. “utf8”:
UTF8 mode
b. “nsc”:
Boolean (non separator constraint)
c. “vect”:
Integer (vectorization threshold)
d. “unknownbychars”:
Boolean (false=BYTOKEN, true=BYCHAR)
You load
an ntm script at creation time:
ntm n('C:\XIP\Test\kifcxx\phonologie\ntmscriptkif',true);
The first
parameter is the ntm script pathname. The second parameter is the lexicon
encoding.
a)
true:
the encoding is utf8
b)
false:
the encoding is not utf8
ntm n('C:\XIP\Test\kifcxx\phonologie\ntmscriptkif',false);
string s="The lady is happy.";
v=n.lookupstring(s);
println(v);
Result:
[['The the +Det+Def+SP+DET'],
['lady lady +Noun+countable+c_person+Sg+NOUN'],
['is be +Verb+Pres+3sg+VBPRES'],
['happy happy +Adj+s_sc_pwith+s_sc_pabout+ADJ'],
['. . +Punct+Sent+SENT']]
KiF provides a specific type to
load a XIP grammar from a KiF program. It is then possible to parse a string
with a specific grammar and display the result of the analysis. In the case of
a call of a KiF program from an application using XIP as an external library,
the results can be hacked through a callback method.
kif_exchange_data is any
object that is passed to XIP, which will then be available within the grammar
as the XIP variable: kif_exchange_data. Thanks to this variable, it is
possible to pass an object through a grammar and use it in a KiF function that
would be called from within this grammar.
1.
addendum(string
filename): Load an
addendum file into the grammar to enrich it.
2.
addendum(string
rules,bool add): Compile a set of XIP rules and add them at the end of the current
grammar, or merge them into the given layers.
3.
addoption(int
nb1,int nb2,...): Add the options of id nb1,nb2 ... See below for a list of these options.
4.
generatefromfile(string
input,kif_exchange_data,string output): generate a sentence out of a
dependency file
5.
generatefromstring(string
input,kif_exchange_data): generate a sentence out of a group of dependencies
6.
generaterule(string
depname,vector focus,vector nodes,vector feats,vector dependencies,vector
dependencyfeatures,bool addquestionmark,int typerule): generate a dependency rule out of
a vector of node, their associated features, a
vector of dependencies and their features. If two nodes are separated by a
sequence of nodes, then with addquestionmark, this sequence is replaced with
“?+” in the rule, or by the category sequence itself otherwise. (see below for
an example). addquestionmark and typerule are optional. Their default
values are respectively false and 0.
7.
getgrammar(int
handler): you can
initialize a parser with a grammar that has been loaded outside the KiF
program. If you do not provide the handler, then the grammar is the current one.
8.
getrulebody(int
n): return the rule
body according to the rule number (see rulenumber for node and dependency).
9.
grammarfiles():
return a map of
all files in a grammar
10.
load(string grmpathname): Load a XIP grammar.
11.
lookup(string s): Apply a simple lookup on the string (similar to ntmonly in XIP). To
disable the tagging process, you can use removeoption(XIP_TAGGER).
12.
name(): Return the pathname of the grm
file
13.
upto(int
nbrule): run the grammar
up to a certain rule number
14.
parse(string
sentence,kif_exchange_data): parse a sentence using the grammar. If no variable is provided to get the
analysis, then the result is displayed on the current output.
15.
parsefeatures(svector
finit,smap fres): translate the FST features in finit into XIP features, to store them in
fres. Return the Part of Speech of the expression if found as a string.
16.
parsefile(string
input,kif_exchange_data,string output): parse the file input
and store the results in string output.
17.
parsefile(string
input,kif_exchange_data,file output): parse the file input and store the
results in file output. Output must have been created as a write file: file
ouput(fname,’w’);
18.
parser var(string grmpathname): Create a parser object and Load a XIP grammar.
19.
parsestructure(map
m|vector v): parse a
predefined tree using the grammar. The tree can also be a vector. The structure
should respect the output of map or of vector on a 'node'.
20.
parsexml(string
input,kif_exchange_data, int depth): parse the XML file input
A callback function must be provided in that case to catch the XipResult
objects.
21.
parsexml(string
input,kif_exchange_data,string output,int depth): parse the XML file input
and store the results in file output. Depth is the depth at which the
analysis should take place.
22.
parsexmlstring(string
input,kif_exchange_data, int depth): parse the XML string input.
23.
reload(): reload the grammar.
24.
removeoption(int
nb1,int nb2,...): Remove the options of id nb1,nb2, nb3 ...
25.
setcatchxml(function):
set the catch
xml function, which is used to catch the extracted string from the XML XIP
internal module. This function should return the new string that will be
analyzed. The function should have the following signature:
a.
function
mycatch(string text) {.... return(newtext);}
26. texttoxml(string input,kif_exchange_data,string
tag,string encoding): transform a text file into an XML file, using 'tag' as the new xml root
and 'encoding', which can take either 'latin' or 'utf8' as a value
XIP uses the following options to guide
the parsing process. Each of the following option is a value, which can be used
in your initialization process.
XIP_ENABLE_DEPENDENCY
XIP_LEMMA
XIP_SURFACE
XIP_MARKUP
XIP_TAGGER
XIP_ENTREE
XIP_CATEGORY
XIP_REDUCED
XIP_FULL
XIP_OFFSET
XIP_WORDNUM
XIP_SENTENCE
XIP_NONE
XIP_DEPENDENCY_BY_NAME
XIP_DEPENDENCY_BY_NODE
XIP_DEPENDENCY_BY_CREATION
XIP_TREE
XIP_TREE_PHRASE
XIP_TREE_COLUMN
XIP_MERGE_XML_SUBTREE
XIP_CONVERSION_UTF8
XIP_EXECUTION_ERROR
XIP_MATHEMATICS
XIP_DEPENDENCY_NUMBER
XIP_UTF8_INPUT
XIP_EXECUTE_TOKEN
XIP_SENTENCE_NUMBER
XIP_LANGUAGE_GUESSER
XIP_NOT_USED
XIP_CHUNK_TREE
XIP_DEPENDENCY_FEATURE_VALUE
XIP_NO_TAG_NORMALISATION
XIP_LOWER_INPUT
XIP_CHECK_INPUT_UTF8
XIP_GENERATION_CATEGORY
XIP_GENERATION
XIP_RANDOM_ANALYSIS
XIP_ JSON
It is also possible to call an external
KiF function through a parser object.
If a grammar is loaded, which implements a KiF program, then the global
functions in that KiF program can be executed from our local KiF program.
Beware that function checking will be
done at run time.
parser p;
p.load(‘c:\grammar\english.grm’);
//we load a grammar implementing Read
string s=p.Read("xxx");
//we can execute Read in our local program.
KiF
exposes the method “generaterule” to generate dependencies rules based on some
nodes from the chunk tree, and a list of dependencies extracted so far.
IMPORTANT: This
method can only be called in a function executed from within XIP, when nodes
and dependencies are still alive.
generaterule(string
depname,vector focus,vector nodes,vector feats,vector dependencies,vector
dependencyfeatures,bool addquestionmark,int typerule)
Parameters:
1)
depname
is the name of the dependency that will
be generated as output of the rule.
2)
focus
is a vector of XIP nodes, which are used
in building the dependencies arguments.
3)
nodes
is the vector of XIP nodes (see type
node), which are supplied to the method, to detect which nodes will be used to
build our rule.
4)
feats is a vector of sub-vectors, where each
sub-vector is associated with its corresponding nodes in the vector of XIP
nodes. Each sub-vector should contain a list of strings of the form:
“attribute:value”. Each can be empty.
5)
dependencies
is a vector of dependency objects (see
type dependency). They will be integrated into a “if” section in the rule when
they are provided.
6)
dependencyfeatures
is a vector of sub-vectors (as feats),
where each sub-vector corresponds to a
dependency above. Each sub-vector should contain a list of strings of the form:
“attribute:value”. Each can be empty.
7)
addquestionmark
is a Boolean, which defines whether the system should replace a sequence of
categories with a “?+”.
8)
typerule is
an integer, which defines the rule type that will be created.
a. DEPENDENCYRULE 0 (default value)
b. SEQUENCERULE 1
c. LEFTCONTEXT 2
d. RIGHTCONTEXT 3
e. IDRULE 4
f. TAGGINGRULE 5
function createrule(node n1,node n2) {
//First we extract the features
ssmap
f1=n1.features();
ssmap
f2=n2.features();
//We then create our feature list. We take ALL
features to create our rule
string f;
svector v1;
for (f in f1)
v1.push(f+":"+f1[f]);
svector v2;
for (f in f2)
v2.push(f+":"+f1[f]);
//We need a vector of sub-vectors
vector
myfeatures=[v1,v2];
vector
mynodes=[n1,n2]; //our
nodes
//Our dependency output will be TOTO, we do not
provide dependencies
string
rule=myparser.generaterule("TOTO",mynodes,myfeatures,[],[],true,0);
println(rule);
}
//Our XIP rule to call createrule
string
rule=@"
script:
//Our nodes
are connected through a SUBJ dependency
if
(subj(#1,#2)) {
createrule(#2,#1);
}
"@;
myparser.addendum(rule,true);
Return the pathname of the grm file
Return the integer handle of the
grammar
Return true if a grammar has been loaded.
parser p;
p.load(‘/home/user/grammar.grm’);
string s=p.parser("This
is an example.",null);
print("S=",s,"\n");
It is also possible to execute a XIP rule from a KiF script.
These rules can be either defined in a KiF section or in XIP file.
In a XIP file, use the keyword: KifDependencies, with as a next
instruction:
rule myrules, where myrules
is a kif variable.
Only dependency rule can be stored in this way.
Each next rule will then be stored in the inner vector of
that rule variable. This variable can be declared as many times as necessary to
store more rules in it. More than one variable can be declared for a given
grammar.
1.
apply(a,b,c…): apply a rule or a group of rules, passing parameters to any KiF
functions that might be called back from the rule itself.
2. get(float weight,float threshold,float value): get the probabilities value of a rule
3. set(float weight,float threshold,float value): set the probabilities value of a rule
KifDependency:
rule myrules;
All the rules declared within this
section after this declaration will be stored in myrules.
if (….)…
Rules can be directly declared in a
KIF programme in the following way:
rule myrule = |Noun#1,Verb#2| if
(~subj(#2,#1)) subj(#2,#1);
To apply a rule, you simply call the
apply method: myrule.apply();
It is also possible to declare a
rule, which in turn will call a KIF function.
rule myrule = |Noun#1,Verb#2|
if (~subj(#2,#1)) {kifcall(x,y);};
x,y parameters can
then be passed through the apply function:
myrule.apply(10,20), from
which kifcall can benefit.
Rules can also be applied one after
the others with an iterator:
iterator it=myrule;
for (it.begin();it.nend();it.next())
it.apply(10,20);